From 625a6cab990f805aceb6fe42771a8c2ed7260498 Mon Sep 17 00:00:00 2001 From: Daniel Ternyak Date: Wed, 27 Feb 2019 16:50:53 -0600 Subject: [PATCH 01/72] don't show popovers, remove delete, add tsc to admin pre-commit --- admin/package.json | 2 +- admin/src/components/ProposalDetail/index.tsx | 62 ++++++++++--------- .../src/components/Proposals/ProposalItem.tsx | 28 +++------ 3 files changed, 42 insertions(+), 50 deletions(-) diff --git a/admin/package.json b/admin/package.json index 7bc7c971..d347c49a 100644 --- a/admin/package.json +++ b/admin/package.json @@ -13,7 +13,7 @@ }, "husky": { "hooks": { - "pre-commit": "lint-staged", + "pre-commit": "lint-staged && yarn run tsc", "pre-push": "yarn run lint && yarn run tsc" } }, diff --git a/admin/src/components/ProposalDetail/index.tsx b/admin/src/components/ProposalDetail/index.tsx index 1794e790..c3549135 100644 --- a/admin/src/components/ProposalDetail/index.tsx +++ b/admin/src/components/ProposalDetail/index.tsx @@ -75,36 +75,42 @@ class ProposalDetailNaked extends React.Component { ); - const renderCancelControl = () => ( - - Are you sure you want to cancel proposal and begin -
- the refund process? This cannot be undone. -

- } - placement="left" - cancelText="cancel" - okText="confirm" - okButtonProps={{ loading: store.proposalDetailCanceling }} - onConfirm={this.handleCancel} - > - -
- ); + + + ); + }; const renderArbiterControl = () => ( { const p = this.props; const status = getStatusById(PROPOSAL_STATUSES, p.status); const stage = getStatusById(PROPOSAL_STAGES, p.stage); - const actions = [ - - delete - , - ]; return ( - +

{p.title || '(no title)'} {status.tagDisplay} - {p.status === PROPOSAL_STATUS.LIVE && + {p.status === PROPOSAL_STATUS.LIVE && ( {stage.tagDisplay} - } + )}

Created: {formatDateSeconds(p.dateCreated)}

{p.brief}

{p.rfp && ( -

Submitted for RFP: {p.rfp.title}

+

+ Submitted for RFP: {p.rfp.title} +

)}
); } - private handleDelete = () => { - store.deleteProposal(this.props.proposalId); - }; } const ProposalItem = view(ProposalItemNaked); From 85ba0d7348951328279704b6d6c6298f1dc84fab Mon Sep 17 00:00:00 2001 From: Daniel Ternyak Date: Wed, 27 Feb 2019 17:05:01 -0600 Subject: [PATCH 02/72] ensure comments are not greater than 1000 chars --- backend/grant/proposal/views.py | 3 +++ backend/tests/proposal/test_comment_api.py | 22 +++++++++++++++++++-- backend/tests/test_data.py | 23 ++++++++++++++++++++-- 3 files changed, 44 insertions(+), 4 deletions(-) diff --git a/backend/grant/proposal/views.py b/backend/grant/proposal/views.py index 308be669..d9f7f7b6 100644 --- a/backend/grant/proposal/views.py +++ b/backend/grant/proposal/views.py @@ -125,6 +125,9 @@ def post_proposal_comments(proposal_id, comment, parent_comment_id): if g.current_user.silenced: return {"message": "Your account has been silenced, commenting is disabled."}, 403 + if len(comment) > 1000: + return {"message": "Please make sure your comment is less than 1000 characters long"}, 400 + # Make the comment comment = Comment( proposal_id=proposal_id, diff --git a/backend/tests/proposal/test_comment_api.py b/backend/tests/proposal/test_comment_api.py index 7ba89fe4..3b3ae791 100644 --- a/backend/tests/proposal/test_comment_api.py +++ b/backend/tests/proposal/test_comment_api.py @@ -2,9 +2,8 @@ import json from grant.proposal.models import Proposal, db from grant.utils.enums import ProposalStatus - from ..config import BaseUserConfig -from ..test_data import test_comment, test_reply +from ..test_data import test_comment, test_reply, test_comment_large class TestProposalCommentAPI(BaseUserConfig): @@ -112,6 +111,25 @@ class TestProposalCommentAPI(BaseUserConfig): self.assertStatus(comment_res, 403) + def test_create_new_proposal_comment_fails_with_large_comment(self): + self.login_default_user() + + proposal = Proposal( + status="LIVE" + ) + db.session.add(proposal) + db.session.commit() + proposal_id = proposal.id + + comment_res = self.app.post( + "/api/v1/proposals/{}/comments".format(proposal_id), + data=json.dumps(test_comment_large), + content_type='application/json' + ) + + self.assertStatus(comment_res, 400) + self.assertIn('less than', comment_res.json['message']) + def test_create_new_proposal_comment_fails_with_silenced_user(self): self.login_default_user() self.user.set_silenced(True) diff --git a/backend/tests/test_data.py b/backend/tests/test_data.py index 2f1c0d54..1ea20484 100644 --- a/backend/tests/test_data.py +++ b/backend/tests/test_data.py @@ -1,6 +1,5 @@ -from .mocks import mock_request from grant.utils.enums import Category - +from .mocks import mock_request test_user = { "displayName": 'Groot', @@ -54,6 +53,26 @@ test_comment = { "comment": "Test comment" } +test_comment_large = { + "comment": """ + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + """ +} + test_reply = { "comment": "Test reply" # Fill in parentCommentId in test From a3d1cbf427dbc1e7d50544a659bbf0a91b2a34aa Mon Sep 17 00:00:00 2001 From: Daniel Ternyak Date: Wed, 27 Feb 2019 21:28:12 -0600 Subject: [PATCH 03/72] replace fake address with real address --- backend/grant/templates/emails/template.html | 4 +++- backend/grant/templates/emails/template.txt | 5 +++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/backend/grant/templates/emails/template.html b/backend/grant/templates/emails/template.html index e7d21a21..12d099f9 100644 --- a/backend/grant/templates/emails/template.html +++ b/backend/grant/templates/emails/template.html @@ -197,7 +197,9 @@

- Zcash Foundation, 123 Address Street, Somewhere, NY 11211 + Zcash Foundation + 1390 Chain Bridge Road, #A132 + McLean, VA 22101

diff --git a/backend/grant/templates/emails/template.txt b/backend/grant/templates/emails/template.txt index e90aef67..4d46ce63 100644 --- a/backend/grant/templates/emails/template.txt +++ b/backend/grant/templates/emails/template.txt @@ -3,7 +3,8 @@ =============== {{ UI.NAME }} -123 Address Street -City, ST 12345 +Zcash Foundation +1390 Chain Bridge Road, #A132 +McLean, VA 22101 Don't want any more emails? Unsubscribe here: {{ args.unsubscribe_url }} \ No newline at end of file From f5d721c0b44407c67abe937bb6607013ca35b45a Mon Sep 17 00:00:00 2001 From: Daniel Ternyak Date: Fri, 1 Mar 2019 11:23:13 -0600 Subject: [PATCH 04/72] Update template.txt --- backend/grant/templates/emails/template.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/backend/grant/templates/emails/template.txt b/backend/grant/templates/emails/template.txt index 4d46ce63..05963720 100644 --- a/backend/grant/templates/emails/template.txt +++ b/backend/grant/templates/emails/template.txt @@ -2,9 +2,8 @@ =============== -{{ UI.NAME }} Zcash Foundation 1390 Chain Bridge Road, #A132 McLean, VA 22101 -Don't want any more emails? Unsubscribe here: {{ args.unsubscribe_url }} \ No newline at end of file +Don't want any more emails? Unsubscribe here: {{ args.unsubscribe_url }} From f950e173974955c1134feb570ec6500c81e7c1f3 Mon Sep 17 00:00:00 2001 From: Daniel Ternyak Date: Fri, 1 Mar 2019 14:11:03 -0600 Subject: [PATCH 05/72] Webargs Request Parsing (#228) --- backend/grant/admin/views.py | 249 ++++++++---------- backend/grant/app.py | 40 ++- backend/grant/blockchain/views.py | 3 +- backend/grant/comment/views.py | 10 - backend/grant/email/views.py | 5 - backend/grant/milestone/views.py | 10 - backend/grant/parser.py | 91 +++++++ backend/grant/proposal/views.py | 176 ++++++------- backend/grant/rfp/models.py | 3 + backend/grant/rfp/views.py | 11 +- backend/grant/user/views.py | 163 ++++++------ backend/requirements/prod.txt | 9 +- .../admin/{test_api.py => test_admin_api.py} | 59 ++++- backend/tests/proposal/test_api.py | 2 +- backend/tests/user/test_user_api.py | 31 +-- 15 files changed, 474 insertions(+), 388 deletions(-) create mode 100644 backend/grant/parser.py rename backend/tests/admin/{test_api.py => test_admin_api.py} (86%) diff --git a/backend/grant/admin/views.py b/backend/grant/admin/views.py index 5c673072..21e775a2 100644 --- a/backend/grant/admin/views.py +++ b/backend/grant/admin/views.py @@ -1,13 +1,18 @@ -from functools import reduce -from flask import Blueprint, request, session -from flask_yoloapi import endpoint, parameter -from decimal import Decimal from datetime import datetime -from sqlalchemy import text +from decimal import Decimal +from functools import reduce +from flask import Blueprint, request +from marshmallow import fields, validate +from sqlalchemy import func, or_ + +import grant.utils.admin as admin +import grant.utils.auth as auth from grant.comment.models import Comment, user_comments_schema, admin_comments_schema, admin_comment_schema from grant.email.send import generate_email, send_email from grant.extensions import db +from grant.milestone.models import Milestone +from grant.parser import body, query, paginated_fields from grant.proposal.models import ( Proposal, ProposalArbiter, @@ -18,12 +23,11 @@ from grant.proposal.models import ( admin_proposal_contribution_schema, admin_proposal_contributions_schema, ) -from grant.milestone.models import Milestone -from grant.user.models import User, UserSettings, admin_users_schema, admin_user_schema from grant.rfp.models import RFP, admin_rfp_schema, admin_rfps_schema -import grant.utils.admin as admin -import grant.utils.auth as auth -from grant.utils.misc import make_url +from grant.settings import EXPLORER_URL +from grant.user.models import User, UserSettings, admin_users_schema, admin_user_schema +from grant.utils import pagination +from grant.utils.enums import Category from grant.utils.enums import ( ProposalStatus, ProposalStage, @@ -32,10 +36,7 @@ from grant.utils.enums import ( MilestoneStage, RFPStatus, ) -from grant.utils import pagination -from grant.settings import EXPLORER_URL -from sqlalchemy import func, or_ - +from grant.utils.misc import make_url from .example_emails import example_email_args blueprint = Blueprint('admin', __name__, url_prefix='/api/v1/admin') @@ -59,16 +60,15 @@ def make_login_state(): @blueprint.route("/checklogin", methods=["GET"]) -@endpoint.api() def loggedin(): return make_login_state() @blueprint.route("/login", methods=["POST"]) -@endpoint.api( - parameter('username', type=str, required=False), - parameter('password', type=str, required=False), -) +@body({ + "username": fields.Str(required=False, missing=None), + "password": fields.Str(required=False, missing=None) +}) def login(username, password): if auth.auth_user(username, password): if admin.admin_is_authed(): @@ -77,9 +77,9 @@ def login(username, password): @blueprint.route("/refresh", methods=["POST"]) -@endpoint.api( - parameter('password', type=str, required=True), -) +@body({ + "password": fields.Str(required=True) +}) def refresh(password): if auth.refresh_auth(password): return make_login_state() @@ -88,7 +88,6 @@ def refresh(password): @blueprint.route("/2fa", methods=["GET"]) -@endpoint.api() def get_2fa(): if not admin.admin_is_authed(): return {"message": "Must be authenticated"}, 403 @@ -96,18 +95,17 @@ def get_2fa(): @blueprint.route("/2fa/init", methods=["GET"]) -@endpoint.api() def get_2fa_init(): admin.throw_on_2fa_not_allowed() return admin.make_2fa_setup() @blueprint.route("/2fa/enable", methods=["POST"]) -@endpoint.api( - parameter('backupCodes', type=list, required=True), - parameter('totpSecret', type=str, required=True), - parameter('verifyCode', type=str, required=True), -) +@body({ + "backupCodes": fields.List(fields.Str(), required=True), + "totpSecret": fields.Str(required=True), + "verifyCode": fields.Str(required=True) +}) def post_2fa_enable(backup_codes, totp_secret, verify_code): admin.throw_on_2fa_not_allowed() admin.check_and_set_2fa_setup(backup_codes, totp_secret, verify_code) @@ -116,9 +114,9 @@ def post_2fa_enable(backup_codes, totp_secret, verify_code): @blueprint.route("/2fa/verify", methods=["POST"]) -@endpoint.api( - parameter('verifyCode', type=str, required=True), -) +@body({ + "verifyCode": fields.Str(required=True) +}) def post_2fa_verify(verify_code): admin.throw_on_2fa_not_allowed(allow_stale=True) admin.admin_auth_2fa(verify_code) @@ -127,7 +125,6 @@ def post_2fa_verify(verify_code): @blueprint.route("/logout", methods=["GET"]) -@endpoint.api() def logout(): admin.logout() return { @@ -137,7 +134,6 @@ def logout(): @blueprint.route("/stats", methods=["GET"]) -@endpoint.api() @admin.admin_auth_required def stats(): user_count = db.session.query(func.count(User.id)).scalar() @@ -162,9 +158,9 @@ def stats(): .filter(ProposalContribution.status == ContributionStatus.CONFIRMED) \ .join(Proposal) \ .filter(or_( - Proposal.stage == ProposalStage.FAILED, - Proposal.stage == ProposalStage.CANCELED, - )) \ + Proposal.stage == ProposalStage.FAILED, + Proposal.stage == ProposalStage.CANCELED, + )) \ .join(ProposalContribution.user) \ .join(UserSettings) \ .filter(UserSettings.refund_address != None) \ @@ -183,7 +179,6 @@ def stats(): @blueprint.route('/users/', methods=['DELETE']) -@endpoint.api() @admin.admin_auth_required def delete_user(user_id): user = User.query.filter(User.id == user_id).first() @@ -192,16 +187,11 @@ def delete_user(user_id): db.session.delete(user) db.session.commit() - return None, 200 + return {"message": "ok"}, 200 @blueprint.route("/users", methods=["GET"]) -@endpoint.api( - parameter('page', type=int, required=False), - parameter('filters', type=list, required=False), - parameter('search', type=str, required=False), - parameter('sort', type=str, required=False) -) +@query(paginated_fields) @admin.admin_auth_required def get_users(page, filters, search, sort): filters_workaround = request.args.getlist('filters[]') @@ -217,7 +207,6 @@ def get_users(page, filters, search, sort): @blueprint.route('/users/', methods=['GET']) -@endpoint.api() @admin.admin_auth_required def get_user(id): user_db = User.query.filter(User.id == id).first() @@ -235,12 +224,12 @@ def get_user(id): @blueprint.route('/users/', methods=['PUT']) -@endpoint.api( - parameter('silenced', type=bool, required=False), - parameter('banned', type=bool, required=False), - parameter('bannedReason', type=str, required=False), - parameter('isAdmin', type=bool, required=False) -) +@body({ + "silenced": fields.Bool(required=False, missing=None), + "banned": fields.Bool(required=False, missing=None), + "bannedReason": fields.Str(required=False, missing=None), + "isAdmin": fields.Bool(required=False, missing=None), +}) @admin.admin_auth_required def edit_user(user_id, silenced, banned, banned_reason, is_admin): user = User.query.filter(User.id == user_id).first() @@ -266,9 +255,9 @@ def edit_user(user_id, silenced, banned, banned_reason, is_admin): @blueprint.route("/arbiters", methods=["GET"]) -@endpoint.api( - parameter('search', type=str, required=False), -) +@query({ + "search": fields.Str(required=False, missing=None) +}) @admin.admin_auth_required def get_arbiters(search): results = [] @@ -289,10 +278,10 @@ def get_arbiters(search): @blueprint.route('/arbiters', methods=['PUT']) -@endpoint.api( - parameter('proposalId', type=int, required=True), - parameter('userId', type=int, required=True) -) +@body({ + "proposalId": fields.Int(required=True), + "userId": fields.Int(required=True), +}) @admin.admin_auth_required def set_arbiter(proposal_id, user_id): proposal = Proposal.query.filter(Proposal.id == proposal_id).first() @@ -323,21 +312,16 @@ def set_arbiter(proposal_id, user_id): db.session.commit() return { - 'proposal': proposal_schema.dump(proposal), - 'user': admin_user_schema.dump(user) - }, 200 + 'proposal': proposal_schema.dump(proposal), + 'user': admin_user_schema.dump(user) + }, 200 # PROPOSALS @blueprint.route("/proposals", methods=["GET"]) -@endpoint.api( - parameter('page', type=int, required=False), - parameter('filters', type=list, required=False), - parameter('search', type=str, required=False), - parameter('sort', type=str, required=False) -) +@query(paginated_fields) @admin.admin_auth_required def get_proposals(page, filters, search, sort): filters_workaround = request.args.getlist('filters[]') @@ -353,7 +337,6 @@ def get_proposals(page, filters, search, sort): @blueprint.route('/proposals/', methods=['GET']) -@endpoint.api() @admin.admin_auth_required def get_proposal(id): proposal = Proposal.query.filter(Proposal.id == id).first() @@ -363,16 +346,15 @@ def get_proposal(id): @blueprint.route('/proposals/', methods=['DELETE']) -@endpoint.api() @admin.admin_auth_required def delete_proposal(id): return {"message": "Not implemented."}, 400 @blueprint.route('/proposals/', methods=['PUT']) -@endpoint.api( - parameter('contributionMatching', type=float, required=False, default=None) -) +@body({ + "contributionMatching": fields.Int(required=False, missing=None) +}) @admin.admin_auth_required def update_proposal(id, contribution_matching): proposal = Proposal.query.filter(Proposal.id == id).first() @@ -389,10 +371,10 @@ def update_proposal(id, contribution_matching): @blueprint.route('/proposals//approve', methods=['PUT']) -@endpoint.api( - parameter('isApprove', type=bool, required=True), - parameter('rejectReason', type=str, required=False) -) +@body({ + "isApprove": fields.Bool(required=True), + "rejectReason": fields.Str(required=False, missing=None) +}) @admin.admin_auth_required def approve_proposal(id, is_approve, reject_reason=None): proposal = Proposal.query.filter_by(id=id).first() @@ -405,7 +387,6 @@ def approve_proposal(id, is_approve, reject_reason=None): @blueprint.route('/proposals//cancel', methods=['PUT']) -@endpoint.api() @admin.admin_auth_required def cancel_proposal(id): proposal = Proposal.query.filter_by(id=id).first() @@ -419,9 +400,9 @@ def cancel_proposal(id): @blueprint.route("/proposals//milestone//paid", methods=["PUT"]) -@endpoint.api( - parameter('txId', type=str, required=True), -) +@body({ + "txId": fields.Str(required=True), +}) @admin.admin_auth_required def paid_milestone_payout_request(id, mid, tx_id): proposal = Proposal.query.filter_by(id=id).first() @@ -459,7 +440,6 @@ def paid_milestone_payout_request(id, mid, tx_id): @blueprint.route('/email/example/', methods=['GET']) -@endpoint.api() @admin.admin_auth_required def get_email_example(type): email = generate_email(type, example_email_args.get(type)) @@ -473,7 +453,6 @@ def get_email_example(type): @blueprint.route('/rfps', methods=['GET']) -@endpoint.api() @admin.admin_auth_required def get_rfps(): rfps = RFP.query.all() @@ -481,15 +460,15 @@ def get_rfps(): @blueprint.route('/rfps', methods=['POST']) -@endpoint.api( - parameter('title', type=str), - parameter('brief', type=str), - parameter('content', type=str), - parameter('category', type=str), - parameter('bounty', type=str), - parameter('matching', type=bool, default=False), - parameter('dateCloses', type=int), -) +@body({ + "title": fields.Str(required=True), + "brief": fields.Str(required=True), + "content": fields.Str(required=True), + "category": fields.Str(required=True, validate=validate.OneOf(choices=Category.list())), + "bounty": fields.Str(required=False, missing=0), + "matching": fields.Bool(required=False, missing=False), + "dateCloses": fields.Int(required=True) +}) @admin.admin_auth_required def create_rfp(date_closes, **kwargs): rfp = RFP( @@ -498,11 +477,10 @@ def create_rfp(date_closes, **kwargs): ) db.session.add(rfp) db.session.commit() - return admin_rfp_schema.dump(rfp), 201 + return admin_rfp_schema.dump(rfp), 200 @blueprint.route('/rfps/', methods=['GET']) -@endpoint.api() @admin.admin_auth_required def get_rfp(rfp_id): rfp = RFP.query.filter(RFP.id == rfp_id).first() @@ -513,16 +491,15 @@ def get_rfp(rfp_id): @blueprint.route('/rfps/', methods=['PUT']) -@endpoint.api( - parameter('title', type=str), - parameter('brief', type=str), - parameter('content', type=str), - parameter('category', type=str), - parameter('bounty', type=str), - parameter('matching', type=bool, default=False), - parameter('dateCloses', type=int), - parameter('status', type=str), -) +@body({ + "title": fields.Str(required=True), + "brief": fields.Str(required=True), + "content": fields.Str(required=True), + "category": fields.Str(required=True, validate=validate.OneOf(choices=Category.list())), + "bounty": fields.Str(required=True), + "matching": fields.Bool(required=True, default=False, missing=False), + "dateCloses": fields.Int(required=True) +}) @admin.admin_auth_required def update_rfp(rfp_id, title, brief, content, category, bounty, matching, date_closes, status): rfp = RFP.query.filter(RFP.id == rfp_id).first() @@ -552,7 +529,6 @@ def update_rfp(rfp_id, title, brief, content, category, bounty, matching, date_c @blueprint.route('/rfps/', methods=['DELETE']) -@endpoint.api() @admin.admin_auth_required def delete_rfp(rfp_id): rfp = RFP.query.filter(RFP.id == rfp_id).first() @@ -561,19 +537,14 @@ def delete_rfp(rfp_id): db.session.delete(rfp) db.session.commit() - return None, 200 + return {"message": "ok"}, 200 # Contributions @blueprint.route('/contributions', methods=['GET']) -@endpoint.api( - parameter('page', type=int, required=False), - parameter('filters', type=list, required=False), - parameter('search', type=str, required=False), - parameter('sort', type=str, required=False) -) +@query(paginated_fields) @admin.admin_auth_required def get_contributions(page, filters, search, sort): filters_workaround = request.args.getlist('filters[]') @@ -588,13 +559,14 @@ def get_contributions(page, filters, search, sort): @blueprint.route('/contributions', methods=['POST']) -@endpoint.api( - parameter('proposalId', type=int, required=True), - parameter('userId', type=int, required=False, default=None), - parameter('status', type=str, required=True), - parameter('amount', type=str, required=True), - parameter('txId', type=str, required=False), -) +@body({ + "proposalId": fields.Int(required=True), + "userId": fields.Int(required=True), + # TODO guard status + "status": fields.Str(required=True), + "amount": fields.Str(required=True), + "txId": fields.Str(required=False, missing=None) +}) @admin.admin_auth_required def create_contribution(proposal_id, user_id, status, amount, tx_id): # Some fields set manually since we're admin, and normally don't do this @@ -603,6 +575,7 @@ def create_contribution(proposal_id, user_id, status, amount, tx_id): user_id=user_id, amount=amount, ) + # TODO guard status contribution.status = status contribution.tx_id = tx_id @@ -617,7 +590,6 @@ def create_contribution(proposal_id, user_id, status, amount, tx_id): @blueprint.route('/contributions/', methods=['GET']) -@endpoint.api() @admin.admin_auth_required def get_contribution(contribution_id): contribution = ProposalContribution.query.filter(ProposalContribution.id == contribution_id).first() @@ -628,14 +600,14 @@ def get_contribution(contribution_id): @blueprint.route('/contributions/', methods=['PUT']) -@endpoint.api( - parameter('proposalId', type=int, required=False), - parameter('userId', type=int, required=False), - parameter('status', type=str, required=False), - parameter('amount', type=str, required=False), - parameter('txId', type=str, required=False), - parameter('refundTxId', type=str, required=False), -) +@body({ + "proposalId": fields.Int(required=False, missing=None), + "userId": fields.Int(required=False, missing=None), + # TODO guard status + "status": fields.Str(required=False, missing=None), + "amount": fields.Str(required=False, missing=None), + "txId": fields.Str(required=False, missing=None) +}) @admin.admin_auth_required def edit_contribution(contribution_id, proposal_id, user_id, status, amount, tx_id, refund_tx_id): contribution = ProposalContribution.query.filter(ProposalContribution.id == contribution_id).first() @@ -694,12 +666,12 @@ def edit_contribution(contribution_id, proposal_id, user_id, status, amount, tx_ @blueprint.route('/comments', methods=['GET']) -@endpoint.api( - parameter('page', type=int, required=False), - parameter('filters', type=list, required=False), - parameter('search', type=str, required=False), - parameter('sort', type=str, required=False) -) +@body({ + "page": fields.Int(required=False, missing=None), + "filters": fields.List(fields.Str(), required=False, missing=None), + "search": fields.Str(required=False, missing=None), + "sort": fields.Str(required=False, missing=None), +}) @admin.admin_auth_required def get_comments(page, filters, search, sort): filters_workaround = request.args.getlist('filters[]') @@ -714,10 +686,11 @@ def get_comments(page, filters, search, sort): @blueprint.route('/comments/', methods=['PUT']) -@endpoint.api( - parameter('hidden', type=bool, required=False), - parameter('reported', type=bool, required=False), -) +@body({ + "hidden": fields.Bool(required=False, missing=None), + "reported": fields.Bool(required=False, missing=None), + +}) @admin.admin_auth_required def edit_comment(comment_id, hidden, reported): comment = Comment.query.filter(Comment.id == comment_id).first() diff --git a/backend/grant/app.py b/backend/grant/app.py index 560581d1..27e4762a 100644 --- a/backend/grant/app.py +++ b/backend/grant/app.py @@ -1,19 +1,54 @@ # -*- coding: utf-8 -*- """The app module, containing the app factory function.""" import sentry_sdk -from flask import Flask +from animal_case import animalify +from flask import Flask, Response, jsonify from flask_cors import CORS from flask_security import SQLAlchemyUserDatastore from flask_sslify import SSLify +from sentry_sdk.integrations.flask import FlaskIntegration + from grant import commands, proposal, user, comment, milestone, admin, email, blockchain, task, rfp from grant.extensions import bcrypt, migrate, db, ma, security from grant.settings import SENTRY_RELEASE, ENV -from sentry_sdk.integrations.flask import FlaskIntegration from grant.utils.auth import AuthException, handle_auth_error, get_authed_user +from grant.utils.exceptions import ValidationException + + +class JSONResponse(Response): + @classmethod + def force_type(cls, rv, environ=None): + if isinstance(rv, dict) or isinstance(rv, list) or isinstance(rv, tuple): + rv = jsonify(animalify(rv)) + elif rv is None: + rv = jsonify(data=None), 204 + + return super(JSONResponse, cls).force_type(rv, environ) def create_app(config_objects=["grant.settings"]): app = Flask(__name__.split(".")[0]) + app.response_class = JSONResponse + + # Return validation errors + @app.errorhandler(ValidationException) + def handle_validation_error(err): + return jsonify({"message": str(err)}), 400 + + @app.errorhandler(422) + @app.errorhandler(400) + def handle_error(err): + headers = err.data.get("headers", None) + messages = err.data.get("messages", "Invalid request.") + error_message = "Something went wrong with your request. That's all we know" + if type(messages) == dict: + if 'json' in messages: + error_message = messages['json'][0] + if headers: + return jsonify({"message": error_message}), err.code, headers + else: + return jsonify({"message": error_message}), err.code + for conf in config_objects: app.config.from_object(conf) app.url_map.strict_slashes = False @@ -85,7 +120,6 @@ def register_commands(app): app.cli.add_command(commands.lint) app.cli.add_command(commands.clean) app.cli.add_command(commands.urls) - app.cli.add_command(proposal.commands.create_proposal) app.cli.add_command(proposal.commands.create_proposals) app.cli.add_command(user.commands.delete_user) diff --git a/backend/grant/blockchain/views.py b/backend/grant/blockchain/views.py index 0d02b075..a120912a 100644 --- a/backend/grant/blockchain/views.py +++ b/backend/grant/blockchain/views.py @@ -1,5 +1,5 @@ from flask import Blueprint -from flask_yoloapi import endpoint + from grant.blockchain.bootstrap import send_bootstrap_data from grant.utils.auth import internal_webhook @@ -8,7 +8,6 @@ blueprint = Blueprint("blockchain", __name__, url_prefix="/api/v1/blockchain") @blueprint.route("/bootstrap", methods=["GET"]) @internal_webhook -@endpoint.api() def get_bootstrap_info(): print('Bootstrap data requested from blockchain watcher microservice...') send_bootstrap_data() diff --git a/backend/grant/comment/views.py b/backend/grant/comment/views.py index c4b8d08f..59af911b 100644 --- a/backend/grant/comment/views.py +++ b/backend/grant/comment/views.py @@ -1,14 +1,4 @@ from flask import Blueprint -from flask_yoloapi import endpoint - -from .models import Comment, comments_schema blueprint = Blueprint("comment", __name__, url_prefix="/api/v1/comment") -# Unused -# @blueprint.route("/", methods=["GET"]) -# @endpoint.api() -# def get_comments(): -# all_comments = Comment.query.all() -# result = comments_schema.dump(all_comments) -# return result diff --git a/backend/grant/email/views.py b/backend/grant/email/views.py index f2e19ebe..e21329a2 100644 --- a/backend/grant/email/views.py +++ b/backend/grant/email/views.py @@ -1,14 +1,11 @@ from flask import Blueprint -from flask_yoloapi import endpoint from .models import EmailVerification, db -from grant.utils.enums import ProposalArbiterStatus blueprint = Blueprint("email", __name__, url_prefix="/api/v1/email") @blueprint.route("//verify", methods=["POST"]) -@endpoint.api() def verify_email(code): ev = EmailVerification.query.filter_by(code=code).first() if ev: @@ -20,7 +17,6 @@ def verify_email(code): @blueprint.route("//unsubscribe", methods=["POST"]) -@endpoint.api() def unsubscribe_email(code): ev = EmailVerification.query.filter_by(code=code).first() if ev: @@ -32,7 +28,6 @@ def unsubscribe_email(code): @blueprint.route("//arbiter/", methods=["POST"]) -@endpoint.api() def accept_arbiter(code, proposal_id): ev = EmailVerification.query.filter_by(code=code).first() if ev: diff --git a/backend/grant/milestone/views.py b/backend/grant/milestone/views.py index 751652b7..99d079d4 100644 --- a/backend/grant/milestone/views.py +++ b/backend/grant/milestone/views.py @@ -1,14 +1,4 @@ from flask import Blueprint -from flask_yoloapi import endpoint - -from .models import Milestone, milestones_schema blueprint = Blueprint('milestone', __name__, url_prefix='/api/v1/milestones') -# Unused -# @blueprint.route("/", methods=["GET"]) -# @endpoint.api() -# def get_milestones(): -# milestones = Milestone.query.all() -# result = milestones_schema.dump(milestones) -# return result diff --git a/backend/grant/parser.py b/backend/grant/parser.py new file mode 100644 index 00000000..dd39013b --- /dev/null +++ b/backend/grant/parser.py @@ -0,0 +1,91 @@ +import functools + +from animal_case import animalify +from webargs.core import dict2schema +from webargs.flaskparser import FlaskParser, abort +from marshmallow import fields + +try: + from collections.abc import Mapping +except ImportError: + from collections import Mapping + + +class Parser(FlaskParser): + DEFAULT_VALIDATION_STATUS = 400 + + def use_kwargs(self, *args, **kwargs): + + kwargs["as_kwargs"] = True + return self.use_args(*args, **kwargs) + + def use_args( + self, + argmap, + req=None, + locations=None, + as_kwargs=False, + validate=None, + error_status_code=None, + error_headers=None, + ): + locations = locations or self.locations + request_obj = req + # Optimization: If argmap is passed as a dictionary, we only need + # to generate a Schema once + if isinstance(argmap, Mapping): + argmap = dict2schema(argmap)() + + def decorator(func): + req_ = request_obj + + @functools.wraps(func) + def wrapper(*args, **kwargs): + req_obj = req_ + + if not req_obj: + req_obj = self.get_request_from_view_args(func, args, kwargs) + # NOTE: At this point, argmap may be a Schema, or a callable + parsed_args = self.parse( + argmap, + req=req_obj, + locations=locations, + validate=validate, + error_status_code=error_status_code, + error_headers=error_headers, + ) + if as_kwargs: + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + # ONLY CHANGE FROM ORIGINAL + kwargs.update(animalify(parsed_args, types='snake')) + # !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! + return func(*args, **kwargs) + else: + # Add parsed_args after other positional arguments + new_args = args + (parsed_args,) + return func(*new_args, **kwargs) + + wrapper.__wrapped__ = func + return wrapper + + return decorator + + def handle_invalid_json_error(self, error, req, *args, **kwargs): + print(error) + abort(400, exc=error, messages={"json": ["Invalid JSON body."]}) + + +parser = Parser() +use_args = parser.use_args +use_kwargs = parser.use_kwargs + +# default kwargs +query = functools.partial(use_kwargs, locations=("query",)) +body = functools.partial(use_kwargs, locations=("json",)) + +paginated_fields = { + "page": fields.Int(required=False, missing=None), + "filters": fields.List(fields.Str(), required=False, missing=None), + "search": fields.Str(required=False, missing=None), + "sort": fields.Str(required=False, missing=None) +} \ No newline at end of file diff --git a/backend/grant/proposal/views.py b/backend/grant/proposal/views.py index e0cd06b1..12164449 100644 --- a/backend/grant/proposal/views.py +++ b/backend/grant/proposal/views.py @@ -1,13 +1,19 @@ -from dateutil.parser import parse +from datetime import datetime from decimal import Decimal + from flask import Blueprint, g, request -from flask_yoloapi import endpoint, parameter +from marshmallow import fields, validate +from sqlalchemy import or_ + from grant.comment.models import Comment, comment_schema, comments_schema from grant.email.send import send_email from grant.milestone.models import Milestone -from grant.settings import EXPLORER_URL, PROPOSAL_STAKING_AMOUNT -from grant.user.models import User +from grant.parser import body, query, paginated_fields from grant.rfp.models import RFP +from grant.settings import EXPLORER_URL, PROPOSAL_STAKING_AMOUNT +from grant.task.jobs import ProposalDeadline +from grant.user.models import User +from grant.utils import pagination from grant.utils.auth import ( requires_auth, requires_team_member_auth, @@ -16,14 +22,10 @@ from grant.utils.auth import ( get_authed_user, internal_webhook ) +from grant.utils.enums import Category +from grant.utils.enums import ProposalStatus, ProposalStage, ContributionStatus from grant.utils.exceptions import ValidationException from grant.utils.misc import is_email, make_url, from_zat -from grant.utils.enums import ProposalStatus, ProposalStage, ContributionStatus -from grant.utils import pagination -from grant.task.jobs import ProposalDeadline -from sqlalchemy import or_ -from datetime import datetime - from .models import ( Proposal, proposals_schema, @@ -43,7 +45,6 @@ blueprint = Blueprint("proposal", __name__, url_prefix="/api/v1/proposals") @blueprint.route("/", methods=["GET"]) -@endpoint.api() def get_proposal(proposal_id): proposal = Proposal.query.filter_by(id=proposal_id).first() if proposal: @@ -60,12 +61,7 @@ def get_proposal(proposal_id): @blueprint.route("//comments", methods=["GET"]) -@endpoint.api( - parameter('page', type=int, required=False), - parameter('filters', type=list, required=False), - parameter('search', type=str, required=False), - parameter('sort', type=str, required=False) -) +@query(paginated_fields) def get_proposal_comments(proposal_id, page, filters, search, sort): # only using page, currently filters_workaround = request.args.getlist('filters[]') @@ -82,7 +78,6 @@ def get_proposal_comments(proposal_id, page, filters, search, sort): @blueprint.route("//comments//report", methods=["PUT"]) @requires_email_verified_auth -@endpoint.api() def report_proposal_comment(proposal_id, comment_id): # Make sure proposal exists proposal = Proposal.query.filter_by(id=proposal_id).first() @@ -95,15 +90,15 @@ def report_proposal_comment(proposal_id, comment_id): comment.report(True) db.session.commit() - return None, 200 + return {"message": "ok"}, 200 @blueprint.route("//comments", methods=["POST"]) @requires_email_verified_auth -@endpoint.api( - parameter('comment', type=str, required=True), - parameter('parentCommentId', type=int, required=False) -) +@body({ + "comment": fields.Str(required=True), + "parentCommentId": fields.Int(required=False, missing=None), +}) def post_proposal_comments(proposal_id, comment, parent_comment_id): # Make sure proposal exists proposal = Proposal.query.filter_by(id=proposal_id).first() @@ -161,12 +156,7 @@ def post_proposal_comments(proposal_id, comment, parent_comment_id): @blueprint.route("/", methods=["GET"]) -@endpoint.api( - parameter('page', type=int, required=False), - parameter('filters', type=list, required=False), - parameter('search', type=str, required=False), - parameter('sort', type=str, required=False) -) +@query(paginated_fields) def get_proposals(page, filters, search, sort): filters_workaround = request.args.getlist('filters[]') query = Proposal.query.filter_by(status=ProposalStatus.LIVE) \ @@ -185,9 +175,9 @@ def get_proposals(page, filters, search, sort): @blueprint.route("/drafts", methods=["POST"]) @requires_email_verified_auth -@endpoint.api( - parameter('rfpId', type=int), -) +@body({ + "rfpId": fields.Int(required=False, missing=None) +}) def make_proposal_draft(rfp_id): proposal = Proposal.create(status=ProposalStatus.DRAFT) proposal.team.append(g.current_user) @@ -209,34 +199,34 @@ def make_proposal_draft(rfp_id): @blueprint.route("/drafts", methods=["GET"]) @requires_auth -@endpoint.api() def get_proposal_drafts(): proposals = ( Proposal.query - .filter(or_( + .filter(or_( Proposal.status == ProposalStatus.DRAFT, Proposal.status == ProposalStatus.REJECTED, )) - .join(proposal_team) - .filter(proposal_team.c.user_id == g.current_user.id) - .order_by(Proposal.date_created.desc()) - .all() + .join(proposal_team) + .filter(proposal_team.c.user_id == g.current_user.id) + .order_by(Proposal.date_created.desc()) + .all() ) return proposals_schema.dump(proposals), 200 @blueprint.route("/", methods=["PUT"]) @requires_team_member_auth -@endpoint.api( - parameter('title', type=str), - parameter('brief', type=str), - parameter('category', type=str), - parameter('content', type=str), - parameter('target', type=str), - parameter('payoutAddress', type=str), - parameter('deadlineDuration', type=int), - parameter('milestones', type=list) -) +# TODO add gaurd (minimum, maximum, shape) +@body({ + "title": fields.Str(required=True), + "brief": fields.Str(required=True), + "category": fields.Str(required=True, validate=validate.OneOf(choices=Category.list())), + "content": fields.Str(required=True), + "target": fields.Str(required=True), + "payoutAddress": fields.Str(required=True), + "deadlineDuration": fields.Int(required=True), + "milestones": fields.List(fields.Dict(), required=True), +}) def update_proposal(milestones, proposal_id, **kwargs): # Update the base proposal fields try: @@ -251,9 +241,9 @@ def update_proposal(milestones, proposal_id, **kwargs): m = Milestone( title=mdata["title"], content=mdata["content"], - date_estimated=datetime.fromtimestamp(mdata["dateEstimated"]), - payout_percent=str(mdata["payoutPercent"]), - immediate_payout=mdata["immediatePayout"], + date_estimated=datetime.fromtimestamp(mdata["date_estimated"]), + payout_percent=str(mdata["payout_percent"]), + immediate_payout=mdata["immediate_payout"], proposal_id=g.current_proposal.id, index=i ) @@ -266,7 +256,6 @@ def update_proposal(milestones, proposal_id, **kwargs): @blueprint.route("//rfp", methods=["DELETE"]) @requires_team_member_auth -@endpoint.api() def unlink_proposal_from_rfp(proposal_id): g.current_proposal.rfp_id = None db.session.add(g.current_proposal) @@ -276,7 +265,6 @@ def unlink_proposal_from_rfp(proposal_id): @blueprint.route("/", methods=["DELETE"]) @requires_team_member_auth -@endpoint.api() def delete_proposal(proposal_id): deleteable_statuses = [ ProposalStatus.DRAFT, @@ -290,12 +278,11 @@ def delete_proposal(proposal_id): return {"message": "Cannot delete proposals with %s status" % status}, 400 db.session.delete(g.current_proposal) db.session.commit() - return None, 202 + return {"message": "ok"}, 202 @blueprint.route("//submit_for_approval", methods=["PUT"]) @requires_team_member_auth -@endpoint.api() def submit_for_approval_proposal(proposal_id): try: g.current_proposal.submit_for_approval() @@ -308,19 +295,17 @@ def submit_for_approval_proposal(proposal_id): @blueprint.route("//stake", methods=["GET"]) @requires_team_member_auth -@endpoint.api() def get_proposal_stake(proposal_id): if g.current_proposal.status != ProposalStatus.STAKING: - return None, 400 + return {"message": "ok"}, 400 contribution = g.current_proposal.get_staking_contribution(g.current_user.id) if contribution: return proposal_contribution_schema.dump(contribution) - return None, 404 + return {"message": "ok"}, 404 @blueprint.route("//publish", methods=["PUT"]) @requires_team_member_auth -@endpoint.api() def publish_proposal(proposal_id): try: g.current_proposal.publish() @@ -336,7 +321,6 @@ def publish_proposal(proposal_id): @blueprint.route("//updates", methods=["GET"]) -@endpoint.api() def get_proposal_updates(proposal_id): proposal = Proposal.query.filter_by(id=proposal_id).first() if proposal: @@ -347,7 +331,6 @@ def get_proposal_updates(proposal_id): @blueprint.route("//updates/", methods=["GET"]) -@endpoint.api() def get_proposal_update(proposal_id, update_id): proposal = Proposal.query.filter_by(id=proposal_id).first() if proposal: @@ -362,10 +345,10 @@ def get_proposal_update(proposal_id, update_id): @blueprint.route("//updates", methods=["POST"]) @requires_team_member_auth -@endpoint.api( - parameter('title', type=str, required=True), - parameter('content', type=str, required=True) -) +@body({ + "title": fields.Str(required=True), + "content": fields.Str(required=True) +}) def post_proposal_update(proposal_id, title, content): update = ProposalUpdate( proposal_id=g.current_proposal.id, @@ -391,9 +374,9 @@ def post_proposal_update(proposal_id, title, content): @blueprint.route("//invite", methods=["POST"]) @requires_team_member_auth -@endpoint.api( - parameter('address', type=str, required=True) -) +@body({ + "address": fields.Str(required=True), +}) def post_proposal_team_invite(proposal_id, address): invite = ProposalTeamInvite( proposal_id=proposal_id, @@ -422,7 +405,6 @@ def post_proposal_team_invite(proposal_id, address): @blueprint.route("//invite/", methods=["DELETE"]) @requires_team_member_auth -@endpoint.api() def delete_proposal_team_invite(proposal_id, id_or_address): invite = ProposalTeamInvite.query.filter( (ProposalTeamInvite.id == id_or_address) | @@ -435,11 +417,10 @@ def delete_proposal_team_invite(proposal_id, id_or_address): db.session.delete(invite) db.session.commit() - return None, 202 + return {"message": "ok"}, 202 @blueprint.route("//contributions", methods=["GET"]) -@endpoint.api() def get_proposal_contributions(proposal_id): proposal = Proposal.query.filter_by(id=proposal_id).first() if not proposal: @@ -447,19 +428,19 @@ def get_proposal_contributions(proposal_id): top_contributions = ProposalContribution.query \ .filter_by( - proposal_id=proposal_id, - status=ContributionStatus.CONFIRMED, - staking=False, - ) \ + proposal_id=proposal_id, + status=ContributionStatus.CONFIRMED, + staking=False, + ) \ .order_by(ProposalContribution.amount.desc()) \ .limit(5) \ .all() latest_contributions = ProposalContribution.query \ .filter_by( - proposal_id=proposal_id, - status=ContributionStatus.CONFIRMED, - staking=False, - ) \ + proposal_id=proposal_id, + status=ContributionStatus.CONFIRMED, + staking=False, + ) \ .order_by(ProposalContribution.date_created.desc()) \ .limit(5) \ .all() @@ -471,7 +452,6 @@ def get_proposal_contributions(proposal_id): @blueprint.route("//contributions/", methods=["GET"]) -@endpoint.api() def get_proposal_contribution(proposal_id, contribution_id): proposal = Proposal.query.filter_by(id=proposal_id).first() if proposal: @@ -485,10 +465,11 @@ def get_proposal_contribution(proposal_id, contribution_id): @blueprint.route("//contributions", methods=["POST"]) -@endpoint.api( - parameter('amount', type=str, required=True), - parameter('anonymous', type=bool, required=False) -) +# TODO add gaurd (minimum, maximum) +@body({ + "amount": fields.Str(required=True), + "anonymous": fields.Bool(required=False, missing=None) +}) def post_proposal_contribution(proposal_id, amount, anonymous): proposal = Proposal.query.filter_by(id=proposal_id).first() if not proposal: @@ -518,11 +499,11 @@ def post_proposal_contribution(proposal_id, amount, anonymous): # Can't use since webhook doesn't know proposal id @blueprint.route("/contribution//confirm", methods=["POST"]) @internal_webhook -@endpoint.api( - parameter('to', type=str, required=True), - parameter('amount', type=str, required=True), - parameter('txid', type=str, required=True), -) +@body({ + "to": fields.Str(required=True), + "amount": fields.Str(required=True), + "txid": fields.Str(required=True), +}) def post_contribution_confirmation(contribution_id, to, amount, txid): contribution = ProposalContribution.query.filter_by( id=contribution_id).first() @@ -534,7 +515,7 @@ def post_contribution_confirmation(contribution_id, to, amount, txid): if contribution.status == ContributionStatus.CONFIRMED: # Duplicates can happen, just return ok - return None, 200 + return {"message": "ok"}, 200 # Convert to whole zcash coins from zats zec_amount = str(from_zat(int(amount))) @@ -581,14 +562,13 @@ def post_contribution_confirmation(contribution_id, to, amount, txid): contribution.proposal.set_funded_when_ready() db.session.commit() - return None, 200 + return {"message": "ok"}, 200 @blueprint.route("/contribution/", methods=["DELETE"]) @requires_auth -@endpoint.api() def delete_proposal_contribution(contribution_id): - contribution = contribution = ProposalContribution.query.filter_by( + contribution = ProposalContribution.query.filter_by( id=contribution_id).first() if not contribution: return {"message": "No contribution matching id"}, 404 @@ -602,13 +582,12 @@ def delete_proposal_contribution(contribution_id): contribution.status = ContributionStatus.DELETED db.session.add(contribution) db.session.commit() - return None, 202 + return {"message": "ok"}, 202 # request MS payout @blueprint.route("//milestone//request", methods=["PUT"]) @requires_team_member_auth -@endpoint.api() def request_milestone_payout(proposal_id, milestone_id): if not g.current_proposal.is_funded: return {"message": "Proposal is not fully funded"}, 400 @@ -630,7 +609,6 @@ def request_milestone_payout(proposal_id, milestone_id): # accept MS payout (arbiter) @blueprint.route("//milestone//accept", methods=["PUT"]) @requires_arbiter_auth -@endpoint.api() def accept_milestone_payout_request(proposal_id, milestone_id): if not g.current_proposal.is_funded: return {"message": "Proposal is not fully funded"}, 400 @@ -655,9 +633,9 @@ def accept_milestone_payout_request(proposal_id, milestone_id): # reject MS payout (arbiter) (reason) @blueprint.route("//milestone//reject", methods=["PUT"]) @requires_arbiter_auth -@endpoint.api( - parameter('reason', type=str, required=True), -) +@body({ + "reason": fields.Str(required=True) +}) def reject_milestone_payout_request(proposal_id, milestone_id, reason): if not g.current_proposal.is_funded: return {"message": "Proposal is not fully funded"}, 400 diff --git a/backend/grant/rfp/models.py b/backend/grant/rfp/models.py index 4861ae50..fc12fa53 100644 --- a/backend/grant/rfp/models.py +++ b/backend/grant/rfp/models.py @@ -2,6 +2,7 @@ from datetime import datetime from grant.extensions import ma, db from grant.utils.enums import RFPStatus from grant.utils.misc import dt_to_unix +from grant.utils.enums import Category class RFP(db.Model): @@ -46,6 +47,8 @@ class RFP(db.Model): matching: bool = False, status: str = RFPStatus.DRAFT, ): + # TODO add status assert + assert Category.includes(category) self.date_created = datetime.now() self.title = title self.brief = brief diff --git a/backend/grant/rfp/views.py b/backend/grant/rfp/views.py index 03c793b9..f60da4d8 100644 --- a/backend/grant/rfp/views.py +++ b/backend/grant/rfp/views.py @@ -1,5 +1,4 @@ -from flask import Blueprint, g -from flask_yoloapi import endpoint, parameter +from flask import Blueprint from sqlalchemy import or_ from grant.utils.enums import RFPStatus @@ -9,20 +8,18 @@ blueprint = Blueprint("rfp", __name__, url_prefix="/api/v1/rfps") @blueprint.route("/", methods=["GET"]) -@endpoint.api() def get_rfps(): rfps = RFP.query \ .filter(or_( - RFP.status == RFPStatus.LIVE, - RFP.status == RFPStatus.CLOSED, - )) \ + RFP.status == RFPStatus.LIVE, + RFP.status == RFPStatus.CLOSED, + )) \ .order_by(RFP.date_created.desc()) \ .all() return rfps_schema.dump(rfps) @blueprint.route("/", methods=["GET"]) -@endpoint.api() def get_rfp(rfp_id): rfp = RFP.query.filter_by(id=rfp_id).first() if not rfp or rfp.status == RFPStatus.DRAFT: diff --git a/backend/grant/user/views.py b/backend/grant/user/views.py index 2fb9b28b..2ed025e1 100644 --- a/backend/grant/user/views.py +++ b/backend/grant/user/views.py @@ -1,8 +1,11 @@ from animal_case import keys_to_snake_case from flask import Blueprint, g -from flask_yoloapi import endpoint, parameter +from marshmallow import fields + +import grant.utils.auth as auth from grant.comment.models import Comment, user_comments_schema from grant.email.models import EmailRecovery +from grant.parser import query, body from grant.proposal.models import ( Proposal, proposal_team, @@ -13,12 +16,10 @@ from grant.proposal.models import ( user_proposals_schema, user_proposal_arbiters_schema ) -import grant.utils.auth as auth +from grant.utils.enums import ProposalStatus, ContributionStatus from grant.utils.exceptions import ValidationException from grant.utils.social import verify_social, get_social_login_url, VerifySocialException from grant.utils.upload import remove_avatar, sign_avatar_upload, AvatarException -from grant.utils.enums import ProposalStatus, ContributionStatus -from flask import current_app from .models import ( User, SocialMedia, @@ -34,9 +35,9 @@ blueprint = Blueprint('user', __name__, url_prefix='/api/v1/users') @blueprint.route("/", methods=["GET"]) -@endpoint.api( - parameter('proposalId', type=str, required=False) -) +@query({ + "proposalId": fields.Str(required=False, missing=None) +}) def get_users(proposal_id): proposal = Proposal.query.filter_by(id=proposal_id).first() if not proposal: @@ -44,10 +45,10 @@ def get_users(proposal_id): else: users = ( User.query - .join(proposal_team) - .join(Proposal) - .filter(proposal_team.c.proposal_id == proposal.id) - .all() + .join(proposal_team) + .join(Proposal) + .filter(proposal_team.c.proposal_id == proposal.id) + .all() ) result = users_schema.dump(users) return result @@ -55,20 +56,19 @@ def get_users(proposal_id): @blueprint.route("/me", methods=["GET"]) @auth.requires_auth -@endpoint.api() def get_me(): dumped_user = self_user_schema.dump(g.current_user) return dumped_user @blueprint.route("/", methods=["GET"]) -@endpoint.api( - parameter("withProposals", type=bool, required=False), - parameter("withComments", type=bool, required=False), - parameter("withFunded", type=bool, required=False), - parameter("withPending", type=bool, required=False), - parameter("withArbitrated", type=bool, required=False) -) +@query({ + "withProposals": fields.Bool(required=False, missing=None), + "withComments": fields.Bool(required=False, missing=None), + "withFunded": fields.Bool(required=False, missing=None), + "withPending": fields.Bool(required=False, missing=None), + "withArbitrated": fields.Bool(required=False, missing=None) +}) def get_user(user_id, with_proposals, with_comments, with_funded, with_pending, with_arbitrated): user = User.get_by_id(user_id) if user: @@ -109,12 +109,13 @@ def get_user(user_id, with_proposals, with_comments, with_funded, with_pending, @blueprint.route("/", methods=["POST"]) -@endpoint.api( - parameter('emailAddress', type=str, required=True), - parameter('password', type=str, required=True), - parameter('displayName', type=str, required=True), - parameter('title', type=str, required=True) -) +@body({ + # TODO guard all (valid, minimum, maximum) + "emailAddress": fields.Str(required=True), + "password": fields.Str(required=True), + "displayName": fields.Str(required=True), + "title": fields.Str(required=True), +}) def create_user( email_address, password, @@ -137,10 +138,10 @@ def create_user( @blueprint.route("/auth", methods=["POST"]) -@endpoint.api( - parameter('email', type=str, required=True), - parameter('password', type=str, required=True) -) +@body({ + "email": fields.Str(required=True), + "password": fields.Str(required=True) +}) def auth_user(email, password): authed_user = auth.auth_user(email, password) return self_user_schema.dump(authed_user) @@ -148,49 +149,48 @@ def auth_user(email, password): @blueprint.route("/me/password", methods=["PUT"]) @auth.requires_auth -@endpoint.api( - parameter('currentPassword', type=str, required=True), - parameter('password', type=str, required=True), -) +# TODO gaurd password (minimum) +@body({ + "currentPassword": fields.Str(required=True), + "password": fields.Str(required=True) +}) def update_user_password(current_password, password): if not g.current_user.check_password(current_password): return {"message": "Current password incorrect"}, 403 g.current_user.set_password(password) - return None, 200 + return {"message": "ok"}, 200 @blueprint.route("/me/email", methods=["PUT"]) @auth.requires_auth -@endpoint.api( - parameter('email', type=str, required=True), - parameter('password', type=str, required=True) -) +# TODO gaurd all (valid, minimum) +@body({ + "email": fields.Str(required=True), + "password": fields.Str(required=True) +}) def update_user_email(email, password): if not g.current_user.check_password(password): return {"message": "Password is incorrect"}, 403 g.current_user.set_email(email) - return None, 200 + return {"message": "ok"}, 200 @blueprint.route("/me/resend-verification", methods=["PUT"]) @auth.requires_auth -@endpoint.api() def resend_email_verification(): g.current_user.send_verification_email() - return None, 200 + return {"message": "ok"}, 200 @blueprint.route("/logout", methods=["POST"]) @auth.requires_auth -@endpoint.api() def logout_user(): auth.logout_current_user() - return None, 200 + return {"message": "ok"}, 200 @blueprint.route("/social//authurl", methods=["GET"]) @auth.requires_auth -@endpoint.api() def get_user_social_auth_url(service): try: return {"url": get_social_login_url(service)} @@ -201,9 +201,9 @@ def get_user_social_auth_url(service): @blueprint.route("/social//verify", methods=["POST"]) @auth.requires_auth -@endpoint.api( - parameter('code', type=str, required=True) -) +@body({ + "code": fields.Str(required=True) +}) def verify_user_social(service, code): try: # 1. verify with 3rd party @@ -227,22 +227,23 @@ def verify_user_social(service, code): @blueprint.route("/recover", methods=["POST"]) -@endpoint.api( - parameter('email', type=str, required=True) -) +@body({ + "email": fields.Str(required=True) +}) def recover_user(email): existing_user = User.get_by_email(email) if not existing_user: return {"message": "No user exists with that email"}, 400 auth.throw_on_banned(existing_user) existing_user.send_recovery_email() - return None, 200 + return {"message": "ok"}, 200 @blueprint.route("/recover/", methods=["POST"]) -@endpoint.api( - parameter('password', type=str, required=True), -) +# TODO gaurd length +@body({ + "password": fields.Str(required=True) +}) def recover_email(code, password): er = EmailRecovery.query.filter_by(code=code).first() if er: @@ -252,16 +253,16 @@ def recover_email(code, password): er.user.set_password(password) db.session.delete(er) db.session.commit() - return None, 200 + return {"message": "ok"}, 200 return {"message": "Invalid reset code"}, 400 @blueprint.route("/avatar", methods=["POST"]) @auth.requires_auth -@endpoint.api( - parameter('mimetype', type=str, required=True) -) +@body({ + "mimetype": fields.Str(required=True) +}) def upload_avatar(mimetype): user = g.current_user try: @@ -273,9 +274,9 @@ def upload_avatar(mimetype): @blueprint.route("/avatar", methods=["DELETE"]) @auth.requires_auth -@endpoint.api( - parameter('url', type=str, required=True) -) +@body({ + "url": fields.Str(required=True) +}) def delete_avatar(url): user = g.current_user remove_avatar(url, user.id) @@ -284,12 +285,13 @@ def delete_avatar(url): @blueprint.route("/", methods=["PUT"]) @auth.requires_auth @auth.requires_same_user_auth -@endpoint.api( - parameter('displayName', type=str, required=True), - parameter('title', type=str, required=True), - parameter('socialMedias', type=list, required=True), - parameter('avatar', type=str, required=True) -) +# TODO gaurd all (minimum, minimum, shape, uri) +@body({ + "displayName": fields.Str(required=True), + "title": fields.Str(required=True), + "socialMedias": fields.List(fields.Dict(), required=True), + "avatar": fields.Str(required=True) +}) def update_user(user_id, display_name, title, social_medias, avatar): user = g.current_user @@ -324,7 +326,6 @@ def update_user(user_id, display_name, title, social_medias, avatar): @blueprint.route("//invites", methods=["GET"]) @auth.requires_same_user_auth -@endpoint.api() def get_user_invites(user_id): invites = ProposalTeamInvite.get_pending_for_user(g.current_user) return invites_with_proposal_schema.dump(invites) @@ -332,9 +333,9 @@ def get_user_invites(user_id): @blueprint.route("//invites//respond", methods=["PUT"]) @auth.requires_same_user_auth -@endpoint.api( - parameter('response', type=bool, required=True) -) +@body({ + "response": fields.Bool(required=True) +}) def respond_to_invite(user_id, invite_id, response): invite = ProposalTeamInvite.query.filter_by(id=invite_id).first() if not invite: @@ -348,22 +349,22 @@ def respond_to_invite(user_id, invite_id, response): db.session.add(invite) db.session.commit() - return None, 200 + return {"message": "ok"}, 200 @blueprint.route("//settings", methods=["GET"]) @auth.requires_same_user_auth -@endpoint.api() def get_user_settings(user_id): return user_settings_schema.dump(g.current_user.settings) @blueprint.route("//settings", methods=["PUT"]) @auth.requires_same_user_auth -@endpoint.api( - parameter('emailSubscriptions', type=dict), - parameter('refundAddress', type=str) -) +# TODO guard all (shape, validity) +@body({ + "emailSubscriptions": fields.Dict(required=True), + "refundAddress": fields.Str(required=False, missing=None) +}) def set_user_settings(user_id, email_subscriptions, refund_address): if email_subscriptions: try: @@ -381,9 +382,9 @@ def set_user_settings(user_id, email_subscriptions, refund_address): @blueprint.route("//arbiter/", methods=["PUT"]) @auth.requires_same_user_auth -@endpoint.api( - parameter('isAccept', type=bool) -) +@body({ + "isAccept": fields.Bool(required=False, missing=None) +}) def set_user_arbiter(user_id, proposal_id, is_accept): try: proposal = Proposal.query.filter_by(id=int(proposal_id)).first() @@ -399,5 +400,3 @@ def set_user_arbiter(user_id, proposal_id, is_accept): except ValidationException as e: return {"message": str(e)}, 400 - - return user_settings_schema.dump(g.current_user.settings) diff --git a/backend/requirements/prod.txt b/backend/requirements/prod.txt index fdf93c60..ccbde94f 100644 --- a/backend/requirements/prod.txt +++ b/backend/requirements/prod.txt @@ -53,9 +53,6 @@ markdownify # email sendgrid==5.6.0 -# input validation -flask-yolo2API==0.2.6 - #sentry sentry-sdk[flask]==0.5.5 @@ -71,5 +68,11 @@ Flask-Security==3.0.0 # oauth requests-oauthlib==1.0.0 +# request parsing +webargs==5.1.2 + # 2fa - totp pyotp==2.2.7 + +# JSON formatting +animal_case==0.4.1 \ No newline at end of file diff --git a/backend/tests/admin/test_api.py b/backend/tests/admin/test_admin_api.py similarity index 86% rename from backend/tests/admin/test_api.py rename to backend/tests/admin/test_admin_api.py index 418d3bc5..7360a51a 100644 --- a/backend/tests/admin/test_api.py +++ b/backend/tests/admin/test_admin_api.py @@ -72,9 +72,9 @@ class TestAdminAPI(BaseProposalCreatorConfig): def assert_autherror(self, resp, contains): # this should be 403 - self.assert500(resp) - print(f'...check that [{resp.json["data"]}] contains [{contains}]') - self.assertTrue(contains in resp.json['data']) + self.assert403(resp) + print(f'...check that [{resp.json["message"]}] contains [{contains}]') + self.assertTrue(contains in resp.json['message']) # happy path (mostly) def test_admin_2fa_setup_flow(self): @@ -245,22 +245,22 @@ class TestAdminAPI(BaseProposalCreatorConfig): def test_update_proposal(self): self.login_admin() # set to 1 (on) - resp_on = self.app.put(f"/api/v1/admin/proposals/{self.proposal.id}", data={"contributionMatching": 1}) + resp_on = self.app.put(f"/api/v1/admin/proposals/{self.proposal.id}", data=json.dumps({"contributionMatching": 1})) self.assert200(resp_on) self.assertEqual(resp_on.json['contributionMatching'], 1) - resp_off = self.app.put(f"/api/v1/admin/proposals/{self.proposal.id}", data={"contributionMatching": 0}) + resp_off = self.app.put(f"/api/v1/admin/proposals/{self.proposal.id}", data=json.dumps({"contributionMatching": 0})) self.assert200(resp_off) self.assertEqual(resp_off.json['contributionMatching'], 0) def test_update_proposal_no_auth(self): - resp = self.app.put(f"/api/v1/admin/proposals/{self.proposal.id}", data={"contributionMatching": 1}) + resp = self.app.put(f"/api/v1/admin/proposals/{self.proposal.id}", data=json.dumps({"contributionMatching": 1})) self.assert401(resp) def test_update_proposal_bad_matching(self): self.login_admin() - resp = self.app.put(f"/api/v1/admin/proposals/{self.proposal.id}", data={"contributionMatching": 2}) - self.assert500(resp) - self.assertIn('Bad value', resp.json['data']) + resp = self.app.put(f"/api/v1/admin/proposals/{self.proposal.id}", data=json.dumps({"contributionMatching": 2})) + self.assert400(resp) + self.assertTrue(resp.json['message']) @patch('requests.get', side_effect=mock_blockchain_api_requests) def test_approve_proposal(self, mock_get): @@ -272,7 +272,7 @@ class TestAdminAPI(BaseProposalCreatorConfig): # approve resp = self.app.put( "/api/v1/admin/proposals/{}/approve".format(self.proposal.id), - data={"isApprove": True} + data=json.dumps({"isApprove": True}) ) self.assert200(resp) self.assertEqual(resp.json["status"], ProposalStatus.APPROVED) @@ -287,7 +287,7 @@ class TestAdminAPI(BaseProposalCreatorConfig): # reject resp = self.app.put( "/api/v1/admin/proposals/{}/approve".format(self.proposal.id), - data={"isApprove": False, "rejectReason": "Funnzies."} + data=json.dumps({"isApprove": False, "rejectReason": "Funnzies."}) ) self.assert200(resp) self.assertEqual(resp.json["status"], ProposalStatus.REJECTED) @@ -301,10 +301,43 @@ class TestAdminAPI(BaseProposalCreatorConfig): # nominate arbiter resp = self.app.put( "/api/v1/admin/arbiters", - data={ + data=json.dumps({ 'proposalId': self.proposal.id, 'userId': self.other_user.id - } + }) ) self.assert200(resp) # TODO - more tests + + def test_create_rfp_succeeds(self): + self.login_admin() + + resp = self.app.post( + "/api/v1/admin/rfps", + data=json.dumps({ + "brief": "Some brief", + "category": "CORE_DEV", + "content": "CONTENT", + "dateCloses": 1553980004, + "status": "DRAFT", + "title": "TITLE" + }) + ) + self.assert200(resp) + + def test_create_rfp_fails_with_bad_category(self): + self.login_admin() + + resp = self.app.post( + "/api/v1/admin/rfps", + data=json.dumps({ + "brief": "Some brief", + "category": "NOT_CORE_DEV", + "content": "CONTENT", + "dateCloses": 1553980004, + "status": "DRAFT", + "title": "TITLE" + }) + ) + self.assert400(resp) + diff --git a/backend/tests/proposal/test_api.py b/backend/tests/proposal/test_api.py index 3adc28bc..d399e369 100644 --- a/backend/tests/proposal/test_api.py +++ b/backend/tests/proposal/test_api.py @@ -45,7 +45,7 @@ class TestProposalAPI(BaseProposalCreatorConfig): data=json.dumps(new_proposal), content_type='application/json' ) - + print(resp) self.assert200(resp) self.assertEqual(resp.json["title"], new_title) self.assertEqual(self.proposal.title, new_title) diff --git a/backend/tests/user/test_user_api.py b/backend/tests/user/test_user_api.py index 6e67478c..a8456a57 100644 --- a/backend/tests/user/test_user_api.py +++ b/backend/tests/user/test_user_api.py @@ -104,10 +104,10 @@ class TestUserAPI(BaseUserConfig): }), content_type="application/json" ) - # self.assert403(user_auth_resp) - # self.assertTrue(user_auth_resp.json['message'] is not None) - self.assert500(user_auth_resp) - self.assertIn('Invalid pass', user_auth_resp.json['data']) + self.assert403(user_auth_resp) + self.assertTrue(user_auth_resp.json['message'] is not None) + # self.assert500(user_auth_resp) + # self.assertIn('Invalid pass', user_auth_resp.json['data']) def test_user_auth_bad_email(self): user_auth_resp = self.app.post( @@ -118,10 +118,10 @@ class TestUserAPI(BaseUserConfig): }), content_type="application/json" ) - # self.assert400(user_auth_resp) - # self.assertTrue(user_auth_resp.json['message'] is not None) - self.assert500(user_auth_resp) - self.assertIn('No user', user_auth_resp.json['data']) + self.assert403(user_auth_resp) + self.assertTrue(user_auth_resp.json['message'] is not None) + # self.assert500(user_auth_resp) + # self.assertIn('No user', user_auth_resp.json['data']) def test_user_auth_banned(self): self.user.set_banned(True, 'reason for banning') @@ -134,8 +134,8 @@ class TestUserAPI(BaseUserConfig): content_type="application/json" ) # in test mode we get 500s instead of 403 - self.assert500(user_auth_resp) - self.assertIn('banned', user_auth_resp.json['data']) + self.assert403(user_auth_resp) + self.assertIn('banned', user_auth_resp.json['message']) def test_create_user_duplicate_400(self): # self.user is identical to test_user, should throw @@ -152,7 +152,7 @@ class TestUserAPI(BaseUserConfig): self.login_default_user() updated_user = animalify(copy.deepcopy(user_schema.dump(self.user))) updated_user["displayName"] = 'new display name' - updated_user["avatar"] = {} + updated_user["avatar"] = '' # TODO confirm avatar is no longer a dict updated_user["socialMedias"] = [] user_update_resp = self.app.put( @@ -253,8 +253,9 @@ class TestUserAPI(BaseUserConfig): content_type='application/json' ) # 404 outside testing mode - self.assertStatus(response, 500) - self.assertIn('banned', response.json['data']) + self.assertStatus(response, 403) + print(response.json) + self.assertIn('banned', response.json['message']) def test_recover_user_no_user(self): response = self.app.post( @@ -301,8 +302,8 @@ class TestUserAPI(BaseUserConfig): content_type='application/json' ) # 403 outside of testing mode - self.assertStatus(reset_resp, 500) - self.assertIn('banned', reset_resp.json['data']) + self.assertStatus(reset_resp, 403) + self.assertIn('banned', reset_resp.json['message']) @patch('grant.user.views.verify_social') def test_user_verify_social(self, mock_verify_social): From 2cc5ade6730af2565763958f122ad6af50eebb64 Mon Sep 17 00:00:00 2001 From: Will O'Beirne Date: Mon, 4 Mar 2019 13:52:57 -0500 Subject: [PATCH 06/72] Sort homepage rfps by bounty. Fix admin views and some bounty logic. --- backend/grant/admin/views.py | 9 +++++---- backend/grant/app.py | 1 + backend/grant/proposal/models.py | 2 +- frontend/client/components/Home/Requests.tsx | 12 +++++++++++- 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/backend/grant/admin/views.py b/backend/grant/admin/views.py index 21e775a2..0a29ae68 100644 --- a/backend/grant/admin/views.py +++ b/backend/grant/admin/views.py @@ -496,9 +496,10 @@ def get_rfp(rfp_id): "brief": fields.Str(required=True), "content": fields.Str(required=True), "category": fields.Str(required=True, validate=validate.OneOf(choices=Category.list())), - "bounty": fields.Str(required=True), - "matching": fields.Bool(required=True, default=False, missing=False), - "dateCloses": fields.Int(required=True) + "bounty": fields.Str(required=False, missing=""), + "matching": fields.Bool(required=False, default=False, missing=False), + "dateCloses": fields.Int(required=False, missing=None), + "status": fields.Str(required=True, validate=validate.OneOf(choices=RFPStatus.list())), }) @admin.admin_auth_required def update_rfp(rfp_id, title, brief, content, category, bounty, matching, date_closes, status): @@ -511,8 +512,8 @@ def update_rfp(rfp_id, title, brief, content, category, bounty, matching, date_c rfp.brief = brief rfp.content = content rfp.category = category - rfp.bounty = bounty rfp.matching = matching + rfp.bounty = bounty if bounty and bounty != "" else None rfp.date_closes = datetime.fromtimestamp(date_closes) if date_closes else None # Update timestamps if status changed diff --git a/backend/grant/app.py b/backend/grant/app.py index 6c1953ec..8724540f 100644 --- a/backend/grant/app.py +++ b/backend/grant/app.py @@ -38,6 +38,7 @@ def create_app(config_objects=["grant.settings"]): @app.errorhandler(422) @app.errorhandler(400) def handle_error(err): + print(err) headers = err.data.get("headers", None) messages = err.data.get("messages", "Invalid request.") error_message = "Something went wrong with your request. That's all we know" diff --git a/backend/grant/proposal/models.py b/backend/grant/proposal/models.py index 518d7a98..5464ec4c 100644 --- a/backend/grant/proposal/models.py +++ b/backend/grant/proposal/models.py @@ -506,7 +506,7 @@ class Proposal(db.Model): # apply matching multiplier funded = Decimal(self.contributed) * Decimal(1 + self.contribution_matching) # apply bounty, if available - if self.rfp: + if self.rfp and self.rfp.bounty and self.rfp.bounty != "": funded = funded + Decimal(self.rfp.bounty) # if funded > target, just set as target if funded > target: diff --git a/frontend/client/components/Home/Requests.tsx b/frontend/client/components/Home/Requests.tsx index 94cb01c6..d99bca24 100644 --- a/frontend/client/components/Home/Requests.tsx +++ b/frontend/client/components/Home/Requests.tsx @@ -1,4 +1,5 @@ import React from 'react'; +import BN from 'bn.js'; import { connect } from 'react-redux'; import { Link } from 'react-router-dom'; import { withNamespaces, WithNamespaces } from 'react-i18next'; @@ -28,7 +29,16 @@ class HomeRequests extends React.Component { render() { const { t, rfps, isFetchingRfps } = this.props; - const activeRfps = (rfps || []).filter(rfp => rfp.status === RFP_STATUS.LIVE).slice(0, 2); + + // 2 live RFPs, sorted by highest bounty first + const activeRfps = (rfps || []) + .filter(rfp => rfp.status === RFP_STATUS.LIVE) + .sort((a, b) => { + const aBounty = a.bounty || new BN(0); + const bBounty = b.bounty || new BN(0); + return bBounty.sub(aBounty).toNumber(); + }) + .slice(0, 2); let content; if (activeRfps.length) { From 58ee787ab8d0f0d099ee01b1b6ff2a977a3459c3 Mon Sep 17 00:00:00 2001 From: Will O'Beirne Date: Mon, 4 Mar 2019 13:56:57 -0500 Subject: [PATCH 07/72] Remove print. --- backend/grant/app.py | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/grant/app.py b/backend/grant/app.py index 8724540f..6c1953ec 100644 --- a/backend/grant/app.py +++ b/backend/grant/app.py @@ -38,7 +38,6 @@ def create_app(config_objects=["grant.settings"]): @app.errorhandler(422) @app.errorhandler(400) def handle_error(err): - print(err) headers = err.data.get("headers", None) messages = err.data.get("messages", "Invalid request.") error_message = "Something went wrong with your request. That's all we know" From d5e059a474b08c7fe4a323e0fd63acac5b2b2e6b Mon Sep 17 00:00:00 2001 From: Will O'Beirne Date: Mon, 4 Mar 2019 14:02:07 -0500 Subject: [PATCH 08/72] Close drawer on navigation. --- frontend/client/components/Header/Drawer.tsx | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/frontend/client/components/Header/Drawer.tsx b/frontend/client/components/Header/Drawer.tsx index 32d41cdc..3445d037 100644 --- a/frontend/client/components/Header/Drawer.tsx +++ b/frontend/client/components/Header/Drawer.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { connect } from 'react-redux'; import { Drawer, Menu } from 'antd'; +import { withRouter, RouteComponentProps } from 'react-router'; import { Link } from 'react-router-dom'; import UserAvatar from 'components/UserAvatar'; import { AppState } from 'store/reducers'; @@ -15,7 +16,7 @@ interface OwnProps { onClose(): void; } -type Props = StateProps & OwnProps; +type Props = StateProps & OwnProps & RouteComponentProps; class HeaderDrawer extends React.Component { componentDidMount() { @@ -26,6 +27,12 @@ class HeaderDrawer extends React.Component { window.removeEventListener('resize', this.props.onClose); } + componentDidUpdate(prevProps: Props) { + if (this.props.location.pathname !== prevProps.location.pathname) { + this.props.onClose(); + } + } + render() { const { isOpen, onClose, user } = this.props; @@ -82,4 +89,4 @@ class HeaderDrawer extends React.Component { export default connect(state => ({ user: state.auth.user, -}))(HeaderDrawer); +}))(withRouter(HeaderDrawer)); From 831488d54d5cbdce40c4b0ebf42e9b9056ca73bc Mon Sep 17 00:00:00 2001 From: Will O'Beirne Date: Mon, 4 Mar 2019 14:09:37 -0500 Subject: [PATCH 09/72] Highlight active page correctly. --- frontend/client/components/Header/Drawer.tsx | 27 ++++++++++++-------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/frontend/client/components/Header/Drawer.tsx b/frontend/client/components/Header/Drawer.tsx index 3445d037..f74e5e9f 100644 --- a/frontend/client/components/Header/Drawer.tsx +++ b/frontend/client/components/Header/Drawer.tsx @@ -34,7 +34,7 @@ class HeaderDrawer extends React.Component { } render() { - const { isOpen, onClose, user } = this.props; + const { isOpen, onClose, user, location } = this.props; let userTitle: React.ReactNode = 'Account'; if (user) { @@ -53,31 +53,36 @@ class HeaderDrawer extends React.Component { placement="left" >
Navigation
- + {user ? ( [ - - Profile + + Profile , - + Settings , - + Sign out , ] ) : ( - - Sign in - + [ + + Sign in + , + + Create account + + ] )} - + Browse proposals - + Start a proposal From 89da528c4a983c493e6799f1443bb5e9559ceeae Mon Sep 17 00:00:00 2001 From: Will O'Beirne Date: Mon, 4 Mar 2019 14:24:13 -0500 Subject: [PATCH 10/72] Dont move to failed if it was canceled. --- backend/grant/task/jobs.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/grant/task/jobs.py b/backend/grant/task/jobs.py index 5d41c28a..82a23a16 100644 --- a/backend/grant/task/jobs.py +++ b/backend/grant/task/jobs.py @@ -63,8 +63,8 @@ class ProposalDeadline: from grant.proposal.models import Proposal proposal = Proposal.query.filter_by(id=task.blob["proposal_id"]).first() - # If it was deleted or successful, just noop out - if not proposal or proposal.is_funded: + # If it was deleted, canceled, or successful, just noop out + if not proposal or proposal.is_funded or proposal.stage != ProposalStage.FUNDING_REQUIRED: return # Otherwise, mark it as failed and inform everyone From 454dcbc901c44f95c70beacf2e080ae5e176e2d7 Mon Sep 17 00:00:00 2001 From: Will O'Beirne Date: Mon, 4 Mar 2019 14:31:05 -0500 Subject: [PATCH 11/72] Default browse proposals filter to funding require proposals. --- frontend/client/modules/proposals/reducers.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frontend/client/modules/proposals/reducers.ts b/frontend/client/modules/proposals/reducers.ts index 1594994c..05b2330f 100644 --- a/frontend/client/modules/proposals/reducers.ts +++ b/frontend/client/modules/proposals/reducers.ts @@ -9,7 +9,7 @@ import { LoadableProposalPage, Moreable, } from 'types'; -import { PROPOSAL_SORT } from 'api/constants'; +import { PROPOSAL_SORT, PROPOSAL_STAGE } from 'api/constants'; export interface ProposalDetail extends Proposal { isRequestingPayout: boolean; @@ -62,7 +62,7 @@ export const INITIAL_STATE: ProposalState = { sort: PROPOSAL_SORT.NEWEST, filters: { category: [], - stage: [], + stage: [PROPOSAL_STAGE.FUNDING_REQUIRED], }, items: [], hasFetched: false, From 4779299fb3005134ca22852bb20474e3d1a8fe76 Mon Sep 17 00:00:00 2001 From: Will O'Beirne Date: Mon, 4 Mar 2019 14:40:37 -0500 Subject: [PATCH 12/72] Reduce payload size for bootstrap. Fix invalid response to bootstrap. Bootstrap height now starts at N - min confs. --- backend/grant/blockchain/bootstrap.py | 8 +++----- backend/grant/blockchain/views.py | 2 +- blockchain/src/node.ts | 3 ++- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/backend/grant/blockchain/bootstrap.py b/backend/grant/blockchain/bootstrap.py index 6d87dba2..98a20b90 100644 --- a/backend/grant/blockchain/bootstrap.py +++ b/backend/grant/blockchain/bootstrap.py @@ -1,9 +1,6 @@ from datetime import datetime, timedelta -from grant.proposal.models import ( - ProposalContribution, - proposal_contributions_schema, -) +from grant.proposal.models import ProposalContribution from grant.utils.requests import blockchain_post from grant.utils.enums import ContributionStatus @@ -17,8 +14,9 @@ def make_bootstrap_data(): .filter_by(status=ContributionStatus.CONFIRMED) \ .order_by(ProposalContribution.date_created.desc()) \ .first() + serialized_pending_contributions = list(map(lambda c: {"id": c.id}, pending_contributions)) return { - "pendingContributions": proposal_contributions_schema.dump(pending_contributions), + "pendingContributions": serialized_pending_contributions, "latestTxId": latest_contribution.tx_id if latest_contribution else None, } diff --git a/backend/grant/blockchain/views.py b/backend/grant/blockchain/views.py index a120912a..8216c1c8 100644 --- a/backend/grant/blockchain/views.py +++ b/backend/grant/blockchain/views.py @@ -11,4 +11,4 @@ blueprint = Blueprint("blockchain", __name__, url_prefix="/api/v1/blockchain") def get_bootstrap_info(): print('Bootstrap data requested from blockchain watcher microservice...') send_bootstrap_data() - return True + return {"message": "ok"}, 200 diff --git a/blockchain/src/node.ts b/blockchain/src/node.ts index 127f6081..4ef43be2 100644 --- a/blockchain/src/node.ts +++ b/blockchain/src/node.ts @@ -194,7 +194,8 @@ export async function getBootstrapBlockHeight(txid: string | undefined) { try { const tx = await node.gettransaction(txid); const block = await node.getblock(tx.blockhash); - return block.height.toString(); + const height = block.height - parseInt(env.MINIMUM_BLOCK_CONFIRMATIONS, 10); + return height.toString(); } catch(err) { console.warn(`Attempted to get block height for tx ${txid} but failed with the following error:\n`, err); console.warn('Falling back to hard-coded starter blocks'); From 9363d53378a0e109c696c3ead5f273ddadf4fc1c Mon Sep 17 00:00:00 2001 From: Will O'Beirne Date: Mon, 4 Mar 2019 14:45:03 -0500 Subject: [PATCH 13/72] Prevent overflow x on homepage. --- frontend/client/components/Home/Intro.less | 1 + 1 file changed, 1 insertion(+) diff --git a/frontend/client/components/Home/Intro.less b/frontend/client/components/Home/Intro.less index 46954808..1082ccb3 100644 --- a/frontend/client/components/Home/Intro.less +++ b/frontend/client/components/Home/Intro.less @@ -8,6 +8,7 @@ max-width: 1440px; padding: 0 4rem; margin: 0 auto 6rem; + overflow: hidden; @media @thin-query { padding: 0 2rem; From 937d894645b1e52aa5d31f66c4d6588a8ee958c6 Mon Sep 17 00:00:00 2001 From: Will O'Beirne Date: Mon, 4 Mar 2019 15:02:51 -0500 Subject: [PATCH 14/72] Require sapling payout address. --- .../client/components/CreateFlow/Payment.tsx | 8 ++++++-- .../client/components/CreateFlow/Review.tsx | 2 +- .../client/components/CreateFlow/example.ts | 2 +- frontend/client/modules/create/utils.ts | 12 +++++++++--- frontend/client/utils/constants.ts | 1 + frontend/client/utils/validators.ts | 18 ++++++++++++++---- 6 files changed, 32 insertions(+), 11 deletions(-) diff --git a/frontend/client/components/CreateFlow/Payment.tsx b/frontend/client/components/CreateFlow/Payment.tsx index 34659ca0..4d754c4b 100644 --- a/frontend/client/components/CreateFlow/Payment.tsx +++ b/frontend/client/components/CreateFlow/Payment.tsx @@ -29,18 +29,22 @@ export default class CreateFlowPayment extends React.Component { render() { const { payoutAddress, deadlineDuration } = this.state; const errors = getCreateErrors(this.state, true); + const payoutHelp = errors.payoutAddress || ` + To ensure the privacy of contributors, this must be a Sapling Z address + `; return (
{ }, { step: CREATE_STEP.PAYMENT, - name: 'Governance', + name: 'Payment', fields: [ { key: 'payoutAddress', diff --git a/frontend/client/components/CreateFlow/example.ts b/frontend/client/components/CreateFlow/example.ts index 31e4a6ae..5c9237a9 100644 --- a/frontend/client/components/CreateFlow/example.ts +++ b/frontend/client/components/CreateFlow/example.ts @@ -14,7 +14,7 @@ const createExampleProposal = (): Partial => { target: '5', // Testnet address, assumes you wouldn't use this in production payoutAddress: - 'ztfFV7AqJqBm1EcWvP3oktZUMnp91ygfduE6ZQqGWENM1CpRKJLMZp2kgChnJVc6CbKSZ4mS37iNaiDwcatxjZcfoi2g7E8', + 'ztestsapling12uyydevkh2gsnnrzuhzz4taw3v544jrpa3v6t7zv4ae45dquau7cfw26u3uzt520m08cs3jxqqs', milestones: [ { title: 'Initial Funding', diff --git a/frontend/client/modules/create/utils.ts b/frontend/client/modules/create/utils.ts index 63938fc6..0c41fddc 100644 --- a/frontend/client/modules/create/utils.ts +++ b/frontend/client/modules/create/utils.ts @@ -6,7 +6,7 @@ import { PROPOSAL_ARBITER_STATUS, } from 'types'; import { User } from 'types'; -import { getAmountError, isValidAddress } from 'utils/validators'; +import { getAmountError, isValidSaplingAddress, isValidTAddress, isValidSproutAddress } from 'utils/validators'; import { Zat, toZat } from 'utils/units'; import { ONE_DAY } from 'utils/time'; import { PROPOSAL_CATEGORY, PROPOSAL_STAGE } from 'api/constants'; @@ -90,8 +90,14 @@ export function getCreateErrors( } // Payout address - if (payoutAddress && !isValidAddress(payoutAddress)) { - errors.payoutAddress = 'That doesn’t look like a valid zcash address'; + if (payoutAddress && !isValidSaplingAddress(payoutAddress)) { + if (isValidSproutAddress(payoutAddress)) { + errors.payoutAddress = 'Must be a Sapling address, not a Sprout address'; + } else if (isValidTAddress(payoutAddress)) { + errors.payoutAddress = 'Must be a Sapling Z address, not a T address'; + } else { + errors.payoutAddress = 'That doesn’t look like a valid Sapling address'; + } } // Milestones diff --git a/frontend/client/utils/constants.ts b/frontend/client/utils/constants.ts index 64316b00..e5271f27 100644 --- a/frontend/client/utils/constants.ts +++ b/frontend/client/utils/constants.ts @@ -1,4 +1,5 @@ export const DONATION = { ZCASH_TRANSPARENT: 't1aib2cbwPVrFfrjGGkhWD67imdBet1xDTr', ZCASH_SPROUT: 'zcWGwZU7FyUgpdrWGkeFqCEnvhLRDAVuf2ZbhW4vzNMTTR6VUgfiBGkiNbkC4e38QaPtS13RKZCriqN9VcyyKNRRQxbgnen', + ZCASH_SAPLING: 'zs15el0hzs4w60ggfy6kq4p3zttjrl00mfq7yxfwsjqpz9d7hptdtkltzlcqar994jg2ju3j9k85zk', }; diff --git a/frontend/client/utils/validators.ts b/frontend/client/utils/validators.ts index d0ebe46e..b7cc434f 100644 --- a/frontend/client/utils/validators.ts +++ b/frontend/client/utils/validators.ts @@ -17,19 +17,29 @@ export function isValidEmail(email: string): boolean { return /\S+@\S+\.\S+/.test(email); } + // Uses simple regex to validate addresses, doesn't check checksum or network -export function isValidAddress(address: string): boolean { - // T address +export function isValidTAddress(address: string): boolean { if (/^t[a-zA-Z0-9]{34}$/.test(address)) { return true; } - // Sprout address + return false; +} + +export function isValidSproutAddress(address: string): boolean { if (/^z[a-zA-Z0-9]{94}$/.test(address)) { return true; } - // Sapling address + return false; +} + +export function isValidSaplingAddress(address: string): boolean { if (/^z(s)?(reg)?(testsapling)?[a-zA-Z0-9]{76}$/.test(address)) { return true; } return false; } + +export function isValidAddress(a: string): boolean { + return isValidTAddress(a) || isValidSproutAddress(a) || isValidSaplingAddress(a); +} From ae625cb3e10d92285bfffb42a2b63e982c8c8bd8 Mon Sep 17 00:00:00 2001 From: Will O'Beirne Date: Mon, 4 Mar 2019 16:47:52 -0500 Subject: [PATCH 15/72] Generate random ID for public facing entities. --- backend/grant/comment/models.py | 2 ++ backend/grant/milestone/models.py | 2 ++ backend/grant/proposal/models.py | 6 +++++- backend/grant/rfp/models.py | 3 ++- backend/grant/user/models.py | 4 +++- backend/grant/utils/misc.py | 13 +++++++++++++ 6 files changed, 27 insertions(+), 3 deletions(-) diff --git a/backend/grant/comment/models.py b/backend/grant/comment/models.py index 4fca9241..c8a9d284 100644 --- a/backend/grant/comment/models.py +++ b/backend/grant/comment/models.py @@ -3,6 +3,7 @@ import datetime from functools import reduce from grant.extensions import ma, db from grant.utils.ma_fields import UnixDate +from grant.utils.misc import gen_random_id from sqlalchemy.orm import raiseload HIDDEN_CONTENT = '~~comment removed by admin~~' @@ -25,6 +26,7 @@ class Comment(db.Model): replies = db.relationship("Comment") def __init__(self, proposal_id, user_id, parent_comment_id, content): + self.id = gen_random_id(Comment) self.proposal_id = proposal_id self.user_id = user_id self.parent_comment_id = parent_comment_id diff --git a/backend/grant/milestone/models.py b/backend/grant/milestone/models.py index 66cf899f..9f92ae9a 100644 --- a/backend/grant/milestone/models.py +++ b/backend/grant/milestone/models.py @@ -4,6 +4,7 @@ from grant.extensions import ma, db from grant.utils.exceptions import ValidationException from grant.utils.ma_fields import UnixDate from grant.utils.enums import MilestoneStage +from grant.utils.misc import gen_random_id class MilestoneException(Exception): @@ -52,6 +53,7 @@ class Milestone(db.Model): stage: str = MilestoneStage.IDLE, proposal_id=int, ): + self.id = gen_random_id(Milestone) self.title = title self.content = content self.stage = stage diff --git a/backend/grant/proposal/models.py b/backend/grant/proposal/models.py index 518d7a98..4f952978 100644 --- a/backend/grant/proposal/models.py +++ b/backend/grant/proposal/models.py @@ -9,7 +9,7 @@ from grant.comment.models import Comment from grant.email.send import send_email from grant.extensions import ma, db from grant.utils.exceptions import ValidationException -from grant.utils.misc import dt_to_unix, make_url +from grant.utils.misc import dt_to_unix, make_url, gen_random_id from grant.utils.requests import blockchain_get from grant.settings import PROPOSAL_STAKING_AMOUNT from grant.utils.enums import ( @@ -64,6 +64,7 @@ class ProposalUpdate(db.Model): content = db.Column(db.Text, nullable=False) def __init__(self, proposal_id: int, title: str, content: str): + self.id = gen_random_id(ProposalUpdate) self.proposal_id = proposal_id self.title = title self.content = content @@ -93,6 +94,7 @@ class ProposalContribution(db.Model): user_id: int = None, staking: bool = False, ): + self.id = gen_random_id(ProposalUpdate) self.proposal_id = proposal_id self.amount = amount self.user_id = user_id @@ -180,6 +182,7 @@ class ProposalArbiter(db.Model): user = db.relationship("User", uselist=False, lazy=True, back_populates="arbiter_proposals") def __init__(self, proposal_id: int, user_id: int = None, status: str = ProposalArbiterStatus.MISSING): + self.id = gen_random_id(ProposalArbiter) self.proposal_id = proposal_id self.user_id = user_id self.status = status @@ -249,6 +252,7 @@ class Proposal(db.Model): deadline_duration: int = 5184000, # 60 days category: str = '' ): + self.id = gen_random_id(Proposal) self.date_created = datetime.datetime.now() self.status = status self.title = title diff --git a/backend/grant/rfp/models.py b/backend/grant/rfp/models.py index fc12fa53..02e0d2be 100644 --- a/backend/grant/rfp/models.py +++ b/backend/grant/rfp/models.py @@ -1,7 +1,7 @@ from datetime import datetime from grant.extensions import ma, db from grant.utils.enums import RFPStatus -from grant.utils.misc import dt_to_unix +from grant.utils.misc import dt_to_unix, gen_random_id from grant.utils.enums import Category @@ -49,6 +49,7 @@ class RFP(db.Model): ): # TODO add status assert assert Category.includes(category) + self.id = gen_random_id(RFP) self.date_created = datetime.now() self.title = title self.brief = brief diff --git a/backend/grant/user/models.py b/backend/grant/user/models.py index 7ef72395..290b90ec 100644 --- a/backend/grant/user/models.py +++ b/backend/grant/user/models.py @@ -10,7 +10,7 @@ from grant.email.subscription_settings import ( email_subscriptions_to_dict ) from grant.extensions import ma, db, security -from grant.utils.misc import make_url +from grant.utils.misc import make_url, gen_random_id from grant.utils.social import generate_social_url from grant.utils.upload import extract_avatar_filename, construct_avatar_url from grant.utils import totp_2fa @@ -96,6 +96,7 @@ class Avatar(db.Model): self._image_url = extract_avatar_filename(image_url) def __init__(self, image_url, user_id): + self.id = gen_random_id(Avatar) self.image_url = image_url self.user_id = user_id @@ -143,6 +144,7 @@ class User(db.Model, UserMixin): display_name=None, title=None, ): + self.id = gen_random_id(User) self.email_address = email_address self.display_name = display_name self.title = title diff --git a/backend/grant/utils/misc.py b/backend/grant/utils/misc.py index 5416924f..1157ac9b 100644 --- a/backend/grant/utils/misc.py +++ b/backend/grant/utils/misc.py @@ -64,3 +64,16 @@ def make_preview(content: str, max_length: int): truncated = True return content + '...' if truncated else content + + +def gen_random_id(model): + min_id = 100000 + max_id = pow(2, 31) - 1 + random_id = random.randint(min_id, max_id) + + # If it already exists, generate a new one (recursively) + existing = model.query.filter_by(id=random_id).first() + if existing: + random_id = gen_random_id(model) + + return random_id From 400774c4d342ad8d9a686d2e41eae7042716994e Mon Sep 17 00:00:00 2001 From: Will O'Beirne Date: Mon, 4 Mar 2019 17:20:47 -0500 Subject: [PATCH 16/72] Remove unused endpoint / test, fix another test. --- backend/grant/user/views.py | 20 -------------------- backend/tests/user/test_user_api.py | 11 ++--------- 2 files changed, 2 insertions(+), 29 deletions(-) diff --git a/backend/grant/user/views.py b/backend/grant/user/views.py index 2ed025e1..4dc0407d 100644 --- a/backend/grant/user/views.py +++ b/backend/grant/user/views.py @@ -34,26 +34,6 @@ from .models import ( blueprint = Blueprint('user', __name__, url_prefix='/api/v1/users') -@blueprint.route("/", methods=["GET"]) -@query({ - "proposalId": fields.Str(required=False, missing=None) -}) -def get_users(proposal_id): - proposal = Proposal.query.filter_by(id=proposal_id).first() - if not proposal: - users = User.query.all() - else: - users = ( - User.query - .join(proposal_team) - .join(Proposal) - .filter(proposal_team.c.proposal_id == proposal.id) - .all() - ) - result = users_schema.dump(users) - return result - - @blueprint.route("/me", methods=["GET"]) @auth.requires_auth def get_me(): diff --git a/backend/tests/user/test_user_api.py b/backend/tests/user/test_user_api.py index a8456a57..f3cd18e7 100644 --- a/backend/tests/user/test_user_api.py +++ b/backend/tests/user/test_user_api.py @@ -34,14 +34,6 @@ class TestUserAPI(BaseUserConfig): # should not be able to add social self.assertFalse(user_db.social_medias) - def test_get_all_users(self): - users_get_resp = self.app.get( - "/api/v1/users/" - ) - self.assert200(users_get_resp) - users_json = users_get_resp.json - self.assertEqual(users_json[0]["displayName"], self.user.display_name) - def test_get_single_user_by_id(self): users_get_resp = self.app.get( "/api/v1/users/{}".format(self.user.id) @@ -163,11 +155,12 @@ class TestUserAPI(BaseUserConfig): self.assert200(user_update_resp, user_update_resp.json) user_json = user_update_resp.json + print(user_json) self.assertFalse(user_json["avatar"]) self.assertFalse(len(user_json["socialMedias"])) self.assertEqual(user_json["displayName"], updated_user["displayName"]) self.assertEqual(user_json["title"], updated_user["title"]) - mock_remove_avatar.assert_called_with(test_user["avatar"]["link"], 1) + mock_remove_avatar.assert_called_with(test_user["avatar"]["link"], self.user.id) def test_update_user_400_when_required_param_not_passed(self): self.login_default_user() From a5ca90874fc6d5f6225e0dd0c5be588567e8638e Mon Sep 17 00:00:00 2001 From: Daniel Ternyak Date: Mon, 4 Mar 2019 19:20:33 -0600 Subject: [PATCH 17/72] Update Payment.tsx --- frontend/client/components/CreateFlow/Payment.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/client/components/CreateFlow/Payment.tsx b/frontend/client/components/CreateFlow/Payment.tsx index 4d754c4b..8c91eed2 100644 --- a/frontend/client/components/CreateFlow/Payment.tsx +++ b/frontend/client/components/CreateFlow/Payment.tsx @@ -30,7 +30,7 @@ export default class CreateFlowPayment extends React.Component { const { payoutAddress, deadlineDuration } = this.state; const errors = getCreateErrors(this.state, true); const payoutHelp = errors.payoutAddress || ` - To ensure the privacy of contributors, this must be a Sapling Z address + This must be a Sapling Z address `; return ( From c600404540257d0ad12433020da445f787db697a Mon Sep 17 00:00:00 2001 From: Aaron Date: Tue, 5 Mar 2019 11:35:40 -0600 Subject: [PATCH 18/72] FE: use Markdown for Profile comments --- frontend/client/components/Profile/ProfileComment.less | 1 - frontend/client/components/Profile/ProfileComment.tsx | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/frontend/client/components/Profile/ProfileComment.less b/frontend/client/components/Profile/ProfileComment.less index e3f0bd64..005ab036 100644 --- a/frontend/client/components/Profile/ProfileComment.less +++ b/frontend/client/components/Profile/ProfileComment.less @@ -1,5 +1,4 @@ .ProfileComment { - padding-bottom: 1.2rem; border-bottom: 1px solid rgba(0, 0, 0, 0.05); margin-bottom: 1rem; diff --git a/frontend/client/components/Profile/ProfileComment.tsx b/frontend/client/components/Profile/ProfileComment.tsx index d4169d4c..a489f069 100644 --- a/frontend/client/components/Profile/ProfileComment.tsx +++ b/frontend/client/components/Profile/ProfileComment.tsx @@ -2,6 +2,8 @@ import React from 'react'; import { Link } from 'react-router-dom'; import moment from 'moment'; import { UserComment } from 'types'; +import Markdown from 'components/Markdown'; +import { MARKDOWN_TYPE } from 'utils/markdown'; import './ProfileComment.less'; interface OwnProps { @@ -28,7 +30,7 @@ export default class Profile extends React.Component { {' '} {moment(dateCreated).from(Date.now())} -
{content}
+ ); } From bbf7b3eab0f0ecc095774a9b178abd57a74f5dd3 Mon Sep 17 00:00:00 2001 From: Aaron Date: Tue, 5 Mar 2019 11:40:25 -0600 Subject: [PATCH 19/72] FE: convert profile comment dateCreated to milliseconds --- frontend/client/components/Profile/ProfileComment.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/client/components/Profile/ProfileComment.tsx b/frontend/client/components/Profile/ProfileComment.tsx index a489f069..9ea219c9 100644 --- a/frontend/client/components/Profile/ProfileComment.tsx +++ b/frontend/client/components/Profile/ProfileComment.tsx @@ -28,7 +28,7 @@ export default class Profile extends React.Component { > {proposal.title} {' '} - {moment(dateCreated).from(Date.now())} + {moment(dateCreated * 1000).from(Date.now())} From 6b1a856145d46389044d515fa99367137a24a3f5 Mon Sep 17 00:00:00 2001 From: Will O'Beirne Date: Wed, 6 Mar 2019 11:16:28 -0500 Subject: [PATCH 20/72] Revert contribution ids to be incremental. --- backend/grant/proposal/models.py | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/grant/proposal/models.py b/backend/grant/proposal/models.py index e1b43e5d..8636e55f 100644 --- a/backend/grant/proposal/models.py +++ b/backend/grant/proposal/models.py @@ -94,7 +94,6 @@ class ProposalContribution(db.Model): user_id: int = None, staking: bool = False, ): - self.id = gen_random_id(ProposalUpdate) self.proposal_id = proposal_id self.amount = amount self.user_id = user_id From 7bbefe8abe83cd7f6044b5797b457c5d5b93e044 Mon Sep 17 00:00:00 2001 From: William O'Beirne Date: Wed, 6 Mar 2019 13:56:01 -0500 Subject: [PATCH 21/72] Update vulnerable packages (#302) * Update frontend dependencies. * Update admin packages. * Fix content editor with new updates. * Regenerate lock files. * Revert "Regenerate lock files." This reverts commit 93150fcac31c3aa10c364fcd023fb425f4ebac0b. * Fix tsc --- admin/package.json | 2 +- admin/yarn.lock | 189 +++++++++----- frontend/client/components/Comment/index.tsx | 1 + .../client/components/CreateFlow/Details.tsx | 1 + .../components/MarkdownEditor/index.tsx | 145 ++++++++--- .../components/MarkdownEditor/style.less | 41 ++- .../components/Proposal/Comments/index.tsx | 2 + .../components/Proposal/UpdateModal/index.tsx | 2 +- frontend/package.json | 8 +- frontend/stories/MarkdownEditor.story.tsx | 9 +- frontend/yarn.lock | 239 ++++++++++++------ 11 files changed, 455 insertions(+), 184 deletions(-) diff --git a/admin/package.json b/admin/package.json index d347c49a..7e44e563 100644 --- a/admin/package.json +++ b/admin/package.json @@ -104,7 +104,7 @@ "url-loader": "^1.1.1", "webpack": "^4.19.0", "webpack-cli": "^3.1.0", - "webpack-dev-server": "^3.1.8", + "webpack-dev-server": "3.2.1", "webpack-hot-middleware": "^2.24.0", "xss": "1.0.3" }, diff --git a/admin/yarn.lock b/admin/yarn.lock index ba321285..ae35d90b 100644 --- a/admin/yarn.lock +++ b/admin/yarn.lock @@ -2631,6 +2631,13 @@ debug@^3.1.0, debug@^3.2.5: dependencies: ms "^2.1.1" +debug@^4.1.0, debug@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" + integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== + dependencies: + ms "^2.1.1" + decamelize@^1.0.0, decamelize@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" @@ -2661,11 +2668,12 @@ deep-is@~0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" -default-gateway@^2.6.0: - version "2.7.2" - resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-2.7.2.tgz#b7ef339e5e024b045467af403d50348db4642d0f" +default-gateway@^4.0.1: + version "4.1.2" + resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-4.1.2.tgz#b49196b51b26609e5d1af636287517a11a9aaf42" + integrity sha512-xhJUAp3u02JsBGovj0V6B6uYhKCUOmiNc8xGmReUwGu77NmvcpxPVB0pCielxMFumO7CmXBG02XjM8HB97k8Hw== dependencies: - execa "^0.10.0" + execa "^1.0.0" ip-regex "^2.1.0" define-properties@^1.1.2: @@ -2741,9 +2749,10 @@ detect-libc@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" -detect-node@^2.0.3: +detect-node@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c" + integrity sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw== detect-port-alt@1.1.6: version "1.1.6" @@ -3165,6 +3174,19 @@ execa@^0.9.0: signal-exit "^3.0.0" strip-eof "^1.0.0" +execa@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" + integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== + dependencies: + cross-spawn "^6.0.0" + get-stream "^4.0.0" + is-stream "^1.1.0" + npm-run-path "^2.0.0" + p-finally "^1.0.0" + signal-exit "^3.0.0" + strip-eof "^1.0.0" + exenv@^1.2.1: version "1.2.2" resolved "https://registry.yarnpkg.com/exenv/-/exenv-1.2.2.tgz#2ae78e85d9894158670b03d47bec1f03bd91bb9d" @@ -3599,6 +3621,13 @@ get-stream@^3.0.0: version "3.0.0" resolved "http://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" +get-stream@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" + integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== + dependencies: + pump "^3.0.0" + get-value@^2.0.3, get-value@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" @@ -3750,9 +3779,10 @@ hammerjs@^2.0.8: resolved "https://registry.yarnpkg.com/hammerjs/-/hammerjs-2.0.8.tgz#04ef77862cff2bb79d30f7692095930222bf60f1" integrity sha1-BO93hiz/K7edMPdpIJWTAiK/YPE= -handle-thing@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-1.2.5.tgz#fd7aad726bf1a5fd16dfc29b2f7a6601d27139c4" +handle-thing@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.0.tgz#0e039695ff50c93fc288557d696f3c1dc6776754" + integrity sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ== har-schema@^2.0.0: version "2.0.0" @@ -3939,18 +3969,20 @@ http-parser-js@>=0.4.0: version "0.5.0" resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.0.tgz#d65edbede84349d0dc30320815a15d39cc3cbbd8" -http-proxy-middleware@~0.18.0: - version "0.18.0" - resolved "http://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-0.18.0.tgz#0987e6bb5a5606e5a69168d8f967a87f15dd8aab" +http-proxy-middleware@^0.19.1: + version "0.19.1" + resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz#183c7dc4aa1479150306498c210cdaf96080a43a" + integrity sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q== dependencies: - http-proxy "^1.16.2" + http-proxy "^1.17.0" is-glob "^4.0.0" - lodash "^4.17.5" - micromatch "^3.1.9" + lodash "^4.17.11" + micromatch "^3.1.10" -http-proxy@^1.16.2: +http-proxy@^1.17.0: version "1.17.0" resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.17.0.tgz#7ad38494658f84605e2f6db4436df410f4e5be9a" + integrity sha512-Taqn+3nNvYRfJ3bGvKfBSRwy1v6eePlm3oc/aWVxZp57DQr5Eq3xhKJi7Z4hZpS8PC3H4qI+Yly5EmFacGuA/g== dependencies: eventemitter3 "^3.0.0" follow-redirects "^1.0.0" @@ -4090,12 +4122,13 @@ inquirer@3.3.0: strip-ansi "^4.0.0" through "^2.3.6" -internal-ip@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-3.0.1.tgz#df5c99876e1d2eb2ea2d74f520e3f669a00ece27" +internal-ip@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-4.2.0.tgz#46e81b638d84c338e5c67e42b1a17db67d0814fa" + integrity sha512-ZY8Rk+hlvFeuMmG5uH1MXhhdeMntmIaxaInvAmzMq/SHV8rv4Kh+6GiQNNDQd0wZFrcO+FiTBo8lui/osKOyJw== dependencies: - default-gateway "^2.6.0" - ipaddr.js "^1.5.2" + default-gateway "^4.0.1" + ipaddr.js "^1.9.0" interpret@^1.1.0: version "1.1.0" @@ -4131,9 +4164,10 @@ ipaddr.js@1.8.0: version "1.8.0" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.8.0.tgz#eaa33d6ddd7ace8f7f6fe0c9ca0440e706738b1e" -ipaddr.js@^1.5.2: - version "1.8.1" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.8.1.tgz#fa4b79fa47fd3def5e3b159825161c0a519c9427" +ipaddr.js@^1.9.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.0.tgz#37df74e430a0e47550fe54a2defe30d8acd95f65" + integrity sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA== is-accessor-descriptor@^0.1.6: version "0.1.6" @@ -4956,7 +4990,7 @@ memoize-one@^5.0.0: resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.0.0.tgz#d55007dffefb8de7546659a1722a5d42e128286e" integrity sha512-7g0+ejkOaI9w5x6LvQwmj68kUj6rxROywPSCqmclG/HBacmFnZqhVscQ8kovkn9FBCNJmOz6SY42+jnvZzDWdw== -memory-fs@^0.4.0, memory-fs@~0.4.1: +memory-fs@^0.4.0, memory-fs@^0.4.1, memory-fs@~0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" dependencies: @@ -4997,7 +5031,7 @@ micromatch@^2.3.11: parse-glob "^3.0.4" regex-cache "^0.4.2" -micromatch@^3.1.10, micromatch@^3.1.4, micromatch@^3.1.8, micromatch@^3.1.9: +micromatch@^3.1.10, micromatch@^3.1.4, micromatch@^3.1.8: version "3.1.10" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" dependencies: @@ -5460,7 +5494,7 @@ object.values@^1.0.4: function-bind "^1.1.0" has "^1.0.1" -obuf@^1.0.0, obuf@^1.1.1: +obuf@^1.0.0, obuf@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" @@ -5985,6 +6019,14 @@ pump@^2.0.0, pump@^2.0.1: end-of-stream "^1.1.0" once "^1.3.1" +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + pumpify@^1.3.3: version "1.5.1" resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce" @@ -6701,7 +6743,7 @@ read-pkg@^4.0.1: parse-json "^4.0.0" pify "^3.0.0" -"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.2.9, readable-stream@^2.3.3, readable-stream@^2.3.6: +"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6: version "2.3.6" resolved "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" dependencies: @@ -6722,6 +6764,15 @@ readable-stream@1.0: isarray "0.0.1" string_decoder "~0.10.x" +readable-stream@^3.0.6: + version "3.2.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.2.0.tgz#de17f229864c120a9f56945756e4f32c4045245d" + integrity sha512-RV20kLjdmpZuTF1INEb9IA3L68Nmi+Ri7ppZqo78wj//Pn62fCoJyV9zalccNzDD/OuJpMG4f+pfMl8+L6QdGw== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + readdirp@^2.0.0: version "2.2.1" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" @@ -7135,7 +7186,7 @@ semver-compare@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" -"semver@2 || 3 || 4 || 5", semver@^5.0.1, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0: +"semver@2 || 3 || 4 || 5", semver@^5.0.1, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0, semver@^5.6.0: version "5.6.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" @@ -7417,28 +7468,28 @@ spdx-license-ids@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.1.tgz#e2a303236cac54b04031fa7a5a79c7e701df852f" -spdy-transport@^2.0.18: - version "2.1.0" - resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-2.1.0.tgz#4bbb15aaffed0beefdd56ad61dbdc8ba3e2cb7a1" +spdy-transport@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31" + integrity sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw== dependencies: - debug "^2.6.8" - detect-node "^2.0.3" + debug "^4.1.0" + detect-node "^2.0.4" hpack.js "^2.1.6" - obuf "^1.1.1" - readable-stream "^2.2.9" - safe-buffer "^5.0.1" - wbuf "^1.7.2" + obuf "^1.1.2" + readable-stream "^3.0.6" + wbuf "^1.7.3" -spdy@^3.4.1: - version "3.4.7" - resolved "https://registry.yarnpkg.com/spdy/-/spdy-3.4.7.tgz#42ff41ece5cc0f99a3a6c28aabb73f5c3b03acbc" +spdy@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/spdy/-/spdy-4.0.0.tgz#81f222b5a743a329aa12cea6a390e60e9b613c52" + integrity sha512-ot0oEGT/PGUpzf/6uk4AWLqkq+irlqHXkrdbk51oWONh3bxQmBuljxPNl66zlRRcIJStWq0QkLUCPOPjgjvU0Q== dependencies: - debug "^2.6.8" - handle-thing "^1.2.5" + debug "^4.1.0" + handle-thing "^2.0.0" http-deceiver "^1.2.7" - safe-buffer "^5.0.1" select-hose "^2.0.0" - spdy-transport "^2.0.18" + spdy-transport "^3.0.0" split-string@^3.0.1, split-string@^3.0.2: version "3.1.0" @@ -7567,6 +7618,13 @@ string_decoder@^1.0.0, string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" +string_decoder@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.2.0.tgz#fe86e738b19544afe70469243b2a1ee9240eae8d" + integrity sha512-6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w== + dependencies: + safe-buffer "~5.1.0" + string_decoder@~0.10.x: version "0.10.31" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" @@ -7622,12 +7680,19 @@ supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" -supports-color@^5.1.0, supports-color@^5.3.0, supports-color@^5.4.0, supports-color@^5.5.0: +supports-color@^5.3.0, supports-color@^5.4.0, supports-color@^5.5.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" dependencies: has-flag "^3.0.0" +supports-color@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" + integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== + dependencies: + has-flag "^3.0.0" + svgo@^1.0.5: version "1.1.1" resolved "https://registry.yarnpkg.com/svgo/-/svgo-1.1.1.tgz#12384b03335bcecd85cfa5f4e3375fed671cb985" @@ -8077,7 +8142,7 @@ use@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" -util-deprecate@~1.0.1: +util-deprecate@^1.0.1, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" @@ -8188,7 +8253,7 @@ watchpack@^1.5.0: graceful-fs "^4.1.2" neo-async "^2.5.0" -wbuf@^1.1.0, wbuf@^1.7.2: +wbuf@^1.1.0, wbuf@^1.7.3: version "1.7.3" resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.3.tgz#c1d8d149316d3ea852848895cb6a0bfe887b87df" dependencies: @@ -8213,31 +8278,33 @@ webpack-cli@^3.1.0: v8-compile-cache "^2.0.2" yargs "^12.0.2" -webpack-dev-middleware@3.4.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.4.0.tgz#1132fecc9026fd90f0ecedac5cbff75d1fb45890" +webpack-dev-middleware@^3.5.1: + version "3.6.0" + resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.6.0.tgz#71f1b04e52ff8d442757af2be3a658237d53a3e5" + integrity sha512-oeXA3m+5gbYbDBGo4SvKpAHJJEGMoekUbHgo1RK7CP1sz7/WOSeu/dWJtSTk+rzDCLkPwQhGocgIq6lQqOyOwg== dependencies: - memory-fs "~0.4.1" + memory-fs "^0.4.1" mime "^2.3.1" range-parser "^1.0.3" webpack-log "^2.0.0" -webpack-dev-server@^3.1.8: - version "3.1.10" - resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.1.10.tgz#507411bee727ee8d2fdffdc621b66a64ab3dea2b" +webpack-dev-server@3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.2.1.tgz#1b45ce3ecfc55b6ebe5e36dab2777c02bc508c4e" + integrity sha512-sjuE4mnmx6JOh9kvSbPYw3u/6uxCLHNWfhWaIPwcXWsvWOPN+nc5baq4i9jui3oOBRXGonK9+OI0jVkaz6/rCw== dependencies: ansi-html "0.0.7" bonjour "^3.5.0" chokidar "^2.0.0" compression "^1.5.2" connect-history-api-fallback "^1.3.0" - debug "^3.1.0" + debug "^4.1.1" del "^3.0.0" express "^4.16.2" html-entities "^1.2.0" - http-proxy-middleware "~0.18.0" + http-proxy-middleware "^0.19.1" import-local "^2.0.0" - internal-ip "^3.0.1" + internal-ip "^4.2.0" ip "^1.1.5" killable "^1.0.0" loglevel "^1.4.1" @@ -8245,13 +8312,15 @@ webpack-dev-server@^3.1.8: portfinder "^1.0.9" schema-utils "^1.0.0" selfsigned "^1.9.1" + semver "^5.6.0" serve-index "^1.7.2" sockjs "0.3.19" sockjs-client "1.3.0" - spdy "^3.4.1" + spdy "^4.0.0" strip-ansi "^3.0.0" - supports-color "^5.1.0" - webpack-dev-middleware "3.4.0" + supports-color "^6.1.0" + url "^0.11.0" + webpack-dev-middleware "^3.5.1" webpack-log "^2.0.0" yargs "12.0.2" diff --git a/frontend/client/components/Comment/index.tsx b/frontend/client/components/Comment/index.tsx index d2d45587..2cd80ab0 100644 --- a/frontend/client/components/Comment/index.tsx +++ b/frontend/client/components/Comment/index.tsx @@ -93,6 +93,7 @@ class Comment extends React.Component {
)} {error && ( diff --git a/frontend/package.json b/frontend/package.json index 0cfa084f..512f49f1 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -108,10 +108,10 @@ "i18next-express-middleware": "^1.4.1", "i18next-node-fs-backend": "^2.1.0", "js-cookie": "^2.2.0", - "less": "^3.7.1", + "less": "3.9.0", "less-loader": "^4.1.0", "lint-staged": "^7.2.2", - "lodash": "^4.17.10", + "lodash": "4.17.11", "markdown-loader": "^4.0.0", "mini-css-extract-plugin": "^0.4.2", "moment": "^2.22.2", @@ -129,7 +129,7 @@ "react-helmet": "^5.2.0", "react-hot-loader": "^4.3.8", "react-i18next": "^8.3.5", - "react-mde": "^5.8.0", + "react-mde": "7.0.3", "react-redux": "^5.0.7", "react-router": "4.4.0-beta.6", "react-router-dom": "4.4.0-beta.6", @@ -156,7 +156,7 @@ "webpack": "^4.19.0", "webpack-bundle-analyzer": "^3.0.2", "webpack-cli": "^3.1.0", - "webpack-dev-server": "^3.1.8", + "webpack-dev-server": "3.2.1", "webpack-hot-middleware": "^2.24.0", "webpack-manifest-plugin": "^2.0.4", "webpack-node-externals": "^1.7.2", diff --git a/frontend/stories/MarkdownEditor.story.tsx b/frontend/stories/MarkdownEditor.story.tsx index f7820490..45dfb3ac 100644 --- a/frontend/stories/MarkdownEditor.story.tsx +++ b/frontend/stories/MarkdownEditor.story.tsx @@ -21,12 +21,16 @@ consequatur? storiesOf('MarkdownEditor', module) .add('basic', () => (
- null} /> + null} minHeight={200} />
)) .add('initial markdown', () => (
- null} /> + null} + minHeight={200} + />
)) .add('type - reduced', () => ( @@ -35,6 +39,7 @@ storiesOf('MarkdownEditor', module) type={MARKDOWN_TYPE.REDUCED} initialMarkdown={initialMarkdown} onChange={_ => null} + minHeight={200} /> )); diff --git a/frontend/yarn.lock b/frontend/yarn.lock index caf53c73..1aef15a7 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -4472,12 +4472,19 @@ debug@3.1.0: dependencies: ms "2.0.0" -debug@^3.1.0: +debug@^3.1.0, debug@^3.2.5: version "3.2.6" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" dependencies: ms "^2.1.1" +debug@^4.1.0, debug@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" + integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== + dependencies: + ms "^2.1.1" + debuglog@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" @@ -4516,11 +4523,12 @@ deep-is@~0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" -default-gateway@^2.6.0: - version "2.7.2" - resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-2.7.2.tgz#b7ef339e5e024b045467af403d50348db4642d0f" +default-gateway@^4.0.1: + version "4.1.2" + resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-4.1.2.tgz#b49196b51b26609e5d1af636287517a11a9aaf42" + integrity sha512-xhJUAp3u02JsBGovj0V6B6uYhKCUOmiNc8xGmReUwGu77NmvcpxPVB0pCielxMFumO7CmXBG02XjM8HB97k8Hw== dependencies: - execa "^0.10.0" + execa "^1.0.0" ip-regex "^2.1.0" defaults@^1.0.3: @@ -4615,9 +4623,10 @@ detect-newline@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2" -detect-node@^2.0.3: +detect-node@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c" + integrity sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw== detect-port-alt@1.1.6: version "1.1.6" @@ -5081,6 +5090,13 @@ eventsource@0.1.6: dependencies: original ">=0.0.5" +eventsource@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/eventsource/-/eventsource-1.0.7.tgz#8fbc72c93fcd34088090bc0a4e64f4b5cee6d8d0" + integrity sha512-4Ln17+vVT0k8aWq+t/bF5arcS3EpT9gYtW66EPacdj/mAFevznsnyoHLPy2BA8gbIQeIHoPsvwmfBftfcG//BQ== + dependencies: + original "^1.0.0" + evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" @@ -5124,6 +5140,19 @@ execa@^0.9.0: signal-exit "^3.0.0" strip-eof "^1.0.0" +execa@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8" + integrity sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA== + dependencies: + cross-spawn "^6.0.0" + get-stream "^4.0.0" + is-stream "^1.1.0" + npm-run-path "^2.0.0" + p-finally "^1.0.0" + signal-exit "^3.0.0" + strip-eof "^1.0.0" + exenv@^1.2.0, exenv@^1.2.1: version "1.2.2" resolved "https://registry.yarnpkg.com/exenv/-/exenv-1.2.2.tgz#2ae78e85d9894158670b03d47bec1f03bd91bb9d" @@ -5349,7 +5378,7 @@ faye-websocket@^0.10.0: dependencies: websocket-driver ">=0.5.1" -faye-websocket@~0.11.0: +faye-websocket@~0.11.0, faye-websocket@~0.11.1: version "0.11.1" resolved "https://registry.yarnpkg.com/faye-websocket/-/faye-websocket-0.11.1.tgz#f0efe18c4f56e4f40afc7e06c719fd5ee6188f38" dependencies: @@ -5815,6 +5844,13 @@ get-stream@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" +get-stream@^4.0.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5" + integrity sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w== + dependencies: + pump "^3.0.0" + get-value@^2.0.3, get-value@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" @@ -6060,9 +6096,10 @@ hammerjs@^2.0.8: version "2.0.8" resolved "https://registry.yarnpkg.com/hammerjs/-/hammerjs-2.0.8.tgz#04ef77862cff2bb79d30f7692095930222bf60f1" -handle-thing@^1.2.5: - version "1.2.5" - resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-1.2.5.tgz#fd7aad726bf1a5fd16dfc29b2f7a6601d27139c4" +handle-thing@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.0.tgz#0e039695ff50c93fc288557d696f3c1dc6776754" + integrity sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ== har-schema@^2.0.0: version "2.0.0" @@ -6356,7 +6393,7 @@ http-proxy-agent@^2.1.0: agent-base "4" debug "3.1.0" -http-proxy-middleware@^0.18.0, http-proxy-middleware@~0.18.0: +http-proxy-middleware@^0.18.0: version "0.18.0" resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-0.18.0.tgz#0987e6bb5a5606e5a69168d8f967a87f15dd8aab" dependencies: @@ -6365,7 +6402,17 @@ http-proxy-middleware@^0.18.0, http-proxy-middleware@~0.18.0: lodash "^4.17.5" micromatch "^3.1.9" -http-proxy@^1.16.2, http-proxy@^1.8.1: +http-proxy-middleware@^0.19.1: + version "0.19.1" + resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-0.19.1.tgz#183c7dc4aa1479150306498c210cdaf96080a43a" + integrity sha512-yHYTgWMQO8VvwNS22eLLloAkvungsKdKTLO8AJlftYIKNfJr3GK3zK0ZCfzDDGUBttdGc8xFy1mCitvNKQtC3Q== + dependencies: + http-proxy "^1.17.0" + is-glob "^4.0.0" + lodash "^4.17.11" + micromatch "^3.1.10" + +http-proxy@^1.16.2, http-proxy@^1.17.0, http-proxy@^1.8.1: version "1.17.0" resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.17.0.tgz#7ad38494658f84605e2f6db4436df410f4e5be9a" dependencies: @@ -6659,16 +6706,22 @@ inquirer@^6.0.0, inquirer@^6.2.0: strip-ansi "^4.0.0" through "^2.3.6" +insert-text-at-cursor@^0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/insert-text-at-cursor/-/insert-text-at-cursor-0.1.2.tgz#fbaf0b4a5c79ff1381b923df0d4e4f9a4f0f266d" + integrity sha512-rl+PSUIXAtuukF0ejIkA6Ev9Sver+MwhNIeA7PWWKXHR4JStisAKkpjsYRABzoYEfNAC73PhqYmWrUYV2mr2YA== + int64-buffer@^0.1.9: version "0.1.10" resolved "https://registry.yarnpkg.com/int64-buffer/-/int64-buffer-0.1.10.tgz#277b228a87d95ad777d07c13832022406a473423" -internal-ip@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-3.0.1.tgz#df5c99876e1d2eb2ea2d74f520e3f669a00ece27" +internal-ip@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/internal-ip/-/internal-ip-4.2.0.tgz#46e81b638d84c338e5c67e42b1a17db67d0814fa" + integrity sha512-ZY8Rk+hlvFeuMmG5uH1MXhhdeMntmIaxaInvAmzMq/SHV8rv4Kh+6GiQNNDQd0wZFrcO+FiTBo8lui/osKOyJw== dependencies: - default-gateway "^2.6.0" - ipaddr.js "^1.5.2" + default-gateway "^4.0.1" + ipaddr.js "^1.9.0" interpret@^1.0.0, interpret@^1.1.0: version "1.1.0" @@ -6714,9 +6767,10 @@ ipaddr.js@1.8.0: version "1.8.0" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.8.0.tgz#eaa33d6ddd7ace8f7f6fe0c9ca0440e706738b1e" -ipaddr.js@^1.5.2: - version "1.8.1" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.8.1.tgz#fa4b79fa47fd3def5e3b159825161c0a519c9427" +ipaddr.js@^1.9.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.0.tgz#37df74e430a0e47550fe54a2defe30d8acd95f65" + integrity sha512-M4Sjn6N/+O6/IXSJseKqHoFc+5FdGJ22sXqnjTpdZweHK64MzEPAyQZyEU3R/KRv2GLoa7nNtg/C2Ev6m7z+eA== is-accessor-descriptor@^0.1.6: version "0.1.6" @@ -7369,9 +7423,10 @@ less-loader@^4.1.0: loader-utils "^1.1.0" pify "^3.0.0" -less@^3.7.1: - version "3.8.1" - resolved "https://registry.yarnpkg.com/less/-/less-3.8.1.tgz#f31758598ef5a1930dd4caefa9e4340641e71e1d" +less@3.9.0: + version "3.9.0" + resolved "https://registry.yarnpkg.com/less/-/less-3.9.0.tgz#b7511c43f37cf57dc87dffd9883ec121289b1474" + integrity sha512-31CmtPEZraNUtuUREYjSqRkeETFdyEHSEPAGq4erDlUXtda7pzNmctdljdIagSb589d/qXGWiiP31R5JVf+v0w== dependencies: clone "^2.1.2" optionalDependencies: @@ -7691,7 +7746,7 @@ lodash.without@~4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.without/-/lodash.without-4.4.0.tgz#3cd4574a00b67bae373a94b748772640507b7aac" -"lodash@>=3.5 <5", lodash@^4.13.1, lodash@^4.15.0, lodash@^4.17.11, lodash@^4.3.0: +lodash@4.17.11, "lodash@>=3.5 <5", lodash@^4.13.1, lodash@^4.15.0, lodash@^4.17.11, lodash@^4.3.0: version "4.17.11" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" @@ -7912,7 +7967,7 @@ memoize-one@^5.0.0: resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.0.0.tgz#d55007dffefb8de7546659a1722a5d42e128286e" integrity sha512-7g0+ejkOaI9w5x6LvQwmj68kUj6rxROywPSCqmclG/HBacmFnZqhVscQ8kovkn9FBCNJmOz6SY42+jnvZzDWdw== -memory-fs@^0.4.0, memory-fs@~0.4.1: +memory-fs@^0.4.0, memory-fs@^0.4.1, memory-fs@~0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" dependencies: @@ -8813,7 +8868,7 @@ object.values@^1.0.4: function-bind "^1.1.0" has "^1.0.1" -obuf@^1.0.0, obuf@^1.1.1: +obuf@^1.0.0, obuf@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" @@ -8910,7 +8965,7 @@ ora@^0.2.3: cli-spinners "^0.1.2" object-assign "^4.0.1" -original@>=0.0.5: +original@>=0.0.5, original@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/original/-/original-1.0.2.tgz#e442a61cffe1c5fd20a65f3261c26663b303f25f" dependencies: @@ -10339,11 +10394,13 @@ react-lifecycles-compat@^3.0.0, react-lifecycles-compat@^3.0.2, react-lifecycles version "3.0.4" resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" -react-mde@^5.8.0: - version "5.8.0" - resolved "https://registry.yarnpkg.com/react-mde/-/react-mde-5.8.0.tgz#af29cad35a289eb45321c3e4450911afa26bc94a" +react-mde@7.0.3: + version "7.0.3" + resolved "https://registry.yarnpkg.com/react-mde/-/react-mde-7.0.3.tgz#7653385fcbaa8bbafe4610860fbbea1ae0c0ab1e" + integrity sha512-WVPrvS5MR3LFBXZoeMDWC2DF63Oeew8SBa+ry3ySe+6bT05A2uUot25Oiux4ACwDBDnlqmoezpvFvcS9y6dcHg== dependencies: classnames "^2.2.5" + insert-text-at-cursor "^0.1.2" npm "^6.1.0" react-media@^1.8.0: @@ -10556,7 +10613,7 @@ read@1, read@~1.0.1, read@~1.0.7: dependencies: mute-stream "~0.0.4" -"readable-stream@1 || 2", readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.6, readable-stream@^2.2.9, readable-stream@^2.3.5, readable-stream@^2.3.6: +"readable-stream@1 || 2", readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.6, readable-stream@^2.3.5, readable-stream@^2.3.6: version "2.3.6" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" dependencies: @@ -10580,6 +10637,15 @@ readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.1.5, readable string_decoder "~1.0.3" util-deprecate "~1.0.1" +readable-stream@^3.0.6: + version "3.2.0" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.2.0.tgz#de17f229864c120a9f56945756e4f32c4045245d" + integrity sha512-RV20kLjdmpZuTF1INEb9IA3L68Nmi+Ri7ppZqo78wj//Pn62fCoJyV9zalccNzDD/OuJpMG4f+pfMl8+L6QdGw== + dependencies: + inherits "^2.0.3" + string_decoder "^1.1.1" + util-deprecate "^1.0.1" + readable-stream@~1.1.10: version "1.1.14" resolved "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" @@ -11217,7 +11283,7 @@ semver-diff@^2.0.0: version "5.5.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.1.tgz#7dfdd8814bdb7cabc7be0fb1d734cfb66c940477" -semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.5.0: +semver@^5.0.3, semver@^5.1.0, semver@^5.3.0, semver@^5.5.0, semver@^5.6.0: version "5.6.0" resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" @@ -11475,6 +11541,18 @@ sockjs-client@1.1.5: json3 "^3.3.2" url-parse "^1.1.8" +sockjs-client@1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.3.0.tgz#12fc9d6cb663da5739d3dc5fb6e8687da95cb177" + integrity sha512-R9jxEzhnnrdxLCNln0xg5uGHqMnkhPSTzUZH2eXcR03S/On9Yvoq2wyUZILRUhZCNVu2PmwWVoyuiPz8th8zbg== + dependencies: + debug "^3.2.5" + eventsource "^1.0.7" + faye-websocket "~0.11.1" + inherits "^2.0.3" + json3 "^3.3.2" + url-parse "^1.4.3" + sockjs@0.3.19: version "0.3.19" resolved "https://registry.yarnpkg.com/sockjs/-/sockjs-0.3.19.tgz#d976bbe800af7bd20ae08598d582393508993c0d" @@ -11616,28 +11694,28 @@ spdx-license-ids@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz#7a7cd28470cc6d3a1cfe6d66886f6bc430d3ac87" -spdy-transport@^2.0.18: - version "2.1.0" - resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-2.1.0.tgz#4bbb15aaffed0beefdd56ad61dbdc8ba3e2cb7a1" +spdy-transport@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31" + integrity sha512-hsLVFE5SjA6TCisWeJXFKniGGOpBgMLmerfO2aCyCU5s7nJ/rpAepqmFifv/GCbSbueEeAJJnmSQ2rKC/g8Fcw== dependencies: - debug "^2.6.8" - detect-node "^2.0.3" + debug "^4.1.0" + detect-node "^2.0.4" hpack.js "^2.1.6" - obuf "^1.1.1" - readable-stream "^2.2.9" - safe-buffer "^5.0.1" - wbuf "^1.7.2" + obuf "^1.1.2" + readable-stream "^3.0.6" + wbuf "^1.7.3" -spdy@^3.4.1: - version "3.4.7" - resolved "https://registry.yarnpkg.com/spdy/-/spdy-3.4.7.tgz#42ff41ece5cc0f99a3a6c28aabb73f5c3b03acbc" +spdy@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/spdy/-/spdy-4.0.0.tgz#81f222b5a743a329aa12cea6a390e60e9b613c52" + integrity sha512-ot0oEGT/PGUpzf/6uk4AWLqkq+irlqHXkrdbk51oWONh3bxQmBuljxPNl66zlRRcIJStWq0QkLUCPOPjgjvU0Q== dependencies: - debug "^2.6.8" - handle-thing "^1.2.5" + debug "^4.1.0" + handle-thing "^2.0.0" http-deceiver "^1.2.7" - safe-buffer "^5.0.1" select-hose "^2.0.0" - spdy-transport "^2.0.18" + spdy-transport "^3.0.0" split-string@^3.0.1, split-string@^3.0.2: version "3.1.0" @@ -11832,6 +11910,13 @@ string_decoder@^1.0.0, string_decoder@~1.0.3: dependencies: safe-buffer "~5.1.0" +string_decoder@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.2.0.tgz#fe86e738b19544afe70469243b2a1ee9240eae8d" + integrity sha512-6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w== + dependencies: + safe-buffer "~5.1.0" + string_decoder@~0.10.x: version "0.10.31" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" @@ -11901,7 +11986,7 @@ supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" -supports-color@^5.1.0, supports-color@^5.2.0, supports-color@^5.3.0: +supports-color@^5.2.0, supports-color@^5.3.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" dependencies: @@ -11913,6 +11998,13 @@ supports-color@^5.4.0: dependencies: has-flag "^3.0.0" +supports-color@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-6.1.0.tgz#0764abc69c63d5ac842dd4867e8d025e880df8f3" + integrity sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ== + dependencies: + has-flag "^3.0.0" + svg-url-loader@^2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/svg-url-loader/-/svg-url-loader-2.3.2.tgz#dd86b26c19fe3b914f04ea10ef39594eade04464" @@ -12564,7 +12656,7 @@ utif@^2.0.1: dependencies: pako "^1.0.5" -util-deprecate@^1.0.2, util-deprecate@~1.0.1: +util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" @@ -12711,7 +12803,7 @@ watchpack@^1.5.0: graceful-fs "^4.1.2" neo-async "^2.5.0" -wbuf@^1.1.0, wbuf@^1.7.2: +wbuf@^1.1.0, wbuf@^1.7.3: version "1.7.3" resolved "https://registry.yarnpkg.com/wbuf/-/wbuf-1.7.3.tgz#c1d8d149316d3ea852848895cb6a0bfe887b87df" dependencies: @@ -12773,18 +12865,6 @@ webpack-cli@^3.1.0: v8-compile-cache "^2.0.0" yargs "^12.0.1" -webpack-dev-middleware@3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.2.0.tgz#a20ceef194873710052da678f3c6ee0aeed92552" - dependencies: - loud-rejection "^1.6.0" - memory-fs "~0.4.1" - mime "^2.3.1" - path-is-absolute "^1.0.0" - range-parser "^1.0.3" - url-join "^4.0.0" - webpack-log "^2.0.0" - webpack-dev-middleware@^3.2.0: version "3.3.0" resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.3.0.tgz#8104daf4d4f65defe06ee2eaaeea612a7c541462" @@ -12796,22 +12876,33 @@ webpack-dev-middleware@^3.2.0: url-join "^4.0.0" webpack-log "^2.0.0" -webpack-dev-server@^3.1.8: - version "3.1.8" - resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.1.8.tgz#eb7a95945d1108170f902604fb3b939533d9daeb" +webpack-dev-middleware@^3.5.1: + version "3.6.0" + resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-3.6.0.tgz#71f1b04e52ff8d442757af2be3a658237d53a3e5" + integrity sha512-oeXA3m+5gbYbDBGo4SvKpAHJJEGMoekUbHgo1RK7CP1sz7/WOSeu/dWJtSTk+rzDCLkPwQhGocgIq6lQqOyOwg== + dependencies: + memory-fs "^0.4.1" + mime "^2.3.1" + range-parser "^1.0.3" + webpack-log "^2.0.0" + +webpack-dev-server@3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-3.2.1.tgz#1b45ce3ecfc55b6ebe5e36dab2777c02bc508c4e" + integrity sha512-sjuE4mnmx6JOh9kvSbPYw3u/6uxCLHNWfhWaIPwcXWsvWOPN+nc5baq4i9jui3oOBRXGonK9+OI0jVkaz6/rCw== dependencies: ansi-html "0.0.7" bonjour "^3.5.0" chokidar "^2.0.0" compression "^1.5.2" connect-history-api-fallback "^1.3.0" - debug "^3.1.0" + debug "^4.1.1" del "^3.0.0" express "^4.16.2" html-entities "^1.2.0" - http-proxy-middleware "~0.18.0" + http-proxy-middleware "^0.19.1" import-local "^2.0.0" - internal-ip "^3.0.1" + internal-ip "^4.2.0" ip "^1.1.5" killable "^1.0.0" loglevel "^1.4.1" @@ -12819,13 +12910,15 @@ webpack-dev-server@^3.1.8: portfinder "^1.0.9" schema-utils "^1.0.0" selfsigned "^1.9.1" + semver "^5.6.0" serve-index "^1.7.2" sockjs "0.3.19" - sockjs-client "1.1.5" - spdy "^3.4.1" + sockjs-client "1.3.0" + spdy "^4.0.0" strip-ansi "^3.0.0" - supports-color "^5.1.0" - webpack-dev-middleware "3.2.0" + supports-color "^6.1.0" + url "^0.11.0" + webpack-dev-middleware "^3.5.1" webpack-log "^2.0.0" yargs "12.0.2" From 1d2228a394dfacd808eadfdf7bb3711148de5012 Mon Sep 17 00:00:00 2001 From: AMStrix Date: Wed, 6 Mar 2019 14:25:58 -0600 Subject: [PATCH 22/72] Flexible Matching/Bounties + KYC (#277) * BE: proposal rfp opt in & proposal bounty * admin: proposal rfp opt in display & modify bounty * FE: proposal rfp opt in / proposal.contributionBounty * fix github merge (so close) * add status field to update_rfp * handle only showing canel and refund popover correctly * undo unneeded change * BE: make sure rfp.bounty is None when it is set to '0' or null during create/update --- admin/src/components/FeedbackModal/index.tsx | 77 +++++++++++---- admin/src/components/ProposalDetail/index.tsx | 95 ++++++++++++++++--- admin/src/store.ts | 6 ++ admin/src/types.ts | 2 + backend/grant/admin/views.py | 28 +++--- backend/grant/proposal/models.py | 33 ++++++- backend/grant/proposal/views.py | 13 ++- backend/grant/rfp/models.py | 23 ++++- backend/migrations/versions/13365ffe910e_.py | 31 ++++++ frontend/client/api/api.ts | 6 +- .../ContributionModal/PaymentInfo.tsx | 6 +- .../components/ContributionModal/index.tsx | 25 +++-- .../client/components/CreateFlow/Basics.tsx | 53 ++++++++++- .../client/components/CreateFlow/Payment.tsx | 4 +- .../client/components/CreateFlow/Review.tsx | 45 ++++++--- .../client/components/CreateFlow/index.less | 25 ++++- frontend/client/components/Header/Drawer.tsx | 42 ++++---- frontend/client/components/Home/Actions.tsx | 2 +- frontend/client/components/Home/Guide.tsx | 37 ++++---- frontend/client/components/Home/Intro.tsx | 6 +- frontend/client/components/Home/Requests.tsx | 18 ++-- .../Proposal/CampaignBlock/index.tsx | 6 +- frontend/client/components/UserRow/index.tsx | 12 +-- frontend/client/index.tsx | 1 - frontend/client/modules/create/utils.ts | 17 +++- frontend/client/utils/api.ts | 4 + frontend/client/utils/constants.ts | 6 +- frontend/client/utils/validators.ts | 1 - frontend/stories/props.tsx | 1 + frontend/types/proposal.ts | 2 + 30 files changed, 466 insertions(+), 161 deletions(-) create mode 100644 backend/migrations/versions/13365ffe910e_.py diff --git a/admin/src/components/FeedbackModal/index.tsx b/admin/src/components/FeedbackModal/index.tsx index 243cf803..b6715603 100644 --- a/admin/src/components/FeedbackModal/index.tsx +++ b/admin/src/components/FeedbackModal/index.tsx @@ -1,18 +1,31 @@ import React, { ReactNode } from 'react'; import { Modal, Input, Button } from 'antd'; import { ModalFuncProps } from 'antd/lib/modal'; -import TextArea from 'antd/lib/input/TextArea'; +import TextArea, { TextAreaProps } from 'antd/lib/input/TextArea'; +import { InputProps } from 'antd/lib/input'; import './index.less'; interface OpenProps extends ModalFuncProps { - label: ReactNode; + label?: ReactNode; + inputProps?: InputProps; + textAreaProps?: TextAreaProps; + type?: 'textArea' | 'input'; onOk: (feedback: string) => void; } const open = (p: OpenProps) => { // NOTE: display=none antd buttons and using our own to control things more const ref = { text: '' }; - const { label, content, okText, cancelText, ...rest } = p; + const { + label, + content, + type, + inputProps, + textAreaProps, + okText, + cancelText, + ...rest + } = p; const modal = Modal.confirm({ maskClosable: true, icon: <>, @@ -21,6 +34,9 @@ const open = (p: OpenProps) => { { @@ -40,7 +56,10 @@ const open = (p: OpenProps) => { // Feedback content interface OwnProps { onChange: (t: string) => void; - label: ReactNode; + label?: ReactNode; + type: 'textArea' | 'input'; + inputProps?: InputProps; + textAreaProps?: TextAreaProps; onOk: ModalFuncProps['onOk']; onCancel: ModalFuncProps['onCancel']; okText?: ReactNode; @@ -58,27 +77,51 @@ type State = typeof STATE; class Feedback extends React.Component { state = STATE; - input: null | TextArea = null; + input: null | TextArea | Input = null; componentDidMount() { if (this.input) this.input.focus(); } render() { const { text } = this.state; - const { label, onOk, onCancel, content, okText, cancelText } = this.props; + const { + label, + type, + textAreaProps, + inputProps, + onOk, + onCancel, + content, + okText, + cancelText, + } = this.props; return (
{content &&

{content}

} -
{label}
- (this.input = ta)} - rows={4} - required={true} - value={text} - onChange={e => { - this.setState({ text: e.target.value }); - this.props.onChange(e.target.value); - }} - /> + {label &&
{label}
} + {type === 'textArea' && ( + (this.input = ta)} + rows={4} + required={true} + value={text} + onChange={e => { + this.setState({ text: e.target.value }); + this.props.onChange(e.target.value); + }} + {...textAreaProps} + /> + )} + {type === 'input' && ( + (this.input = ta)} + value={text} + onChange={e => { + this.setState({ text: e.target.value }); + this.props.onChange(e.target.value); + }} + {...inputProps} + /> + )}
); + const renderBountyControl = () => ( +
+ +
+ ); + const renderApproved = () => p.status === PROPOSAL_STATUS.APPROVED && ( { {renderDeleteControl()} {renderCancelControl()} {renderArbiterControl()} + {renderBountyControl()} {renderMatchingControl()} @@ -419,6 +441,8 @@ class ProposalDetailNaked extends React.Component { {renderDeetItem('contributed', p.contributed)} {renderDeetItem('funded (inc. matching)', p.funded)} {renderDeetItem('matching', p.contributionMatching)} + {renderDeetItem('bounty', p.contributionBounty)} + {renderDeetItem('rfpOptIn', JSON.stringify(p.rfpOptIn))} {renderDeetItem( 'arbiter', <> @@ -453,6 +477,28 @@ class ProposalDetailNaked extends React.Component { ); } + private getCancelAndRefundDisabled = () => { + const { proposalDetail: p } = store; + if (!p) { + return true; + } + return ( + p.status !== PROPOSAL_STATUS.LIVE || + p.stage === PROPOSAL_STAGE.FAILED || + p.stage === PROPOSAL_STAGE.CANCELED || + p.isFailed + ); + }; + + private handleCancelAndRefundClick = () => { + const disabled = this.getCancelAndRefundDisabled(); + if (!disabled) { + if (!this.state.showCancelAndRefundPopover) { + this.setState({ showCancelAndRefundPopover: true }); + } + } + }; + private getIdFromQuery = () => { return Number(this.props.match.params.id); }; @@ -466,9 +512,14 @@ class ProposalDetailNaked extends React.Component { store.deleteProposal(store.proposalDetail.proposalId); }; - private handleCancel = () => { + private handleCancelCancel = () => { + this.setState({ showCancelAndRefundPopover: false }); + }; + + private handleConfirmCancel = () => { if (!store.proposalDetail) return; store.cancelProposal(store.proposalDetail.proposalId); + this.setState({ showCancelAndRefundPopover: false }); }; private handleApprove = () => { @@ -485,7 +536,29 @@ class ProposalDetailNaked extends React.Component { // we lock this to be 1 or 0 for now, we may support more values later on const contributionMatching = store.proposalDetail.contributionMatching === 0 ? 1 : 0; - store.updateProposalDetail({ contributionMatching }); + await store.updateProposalDetail({ contributionMatching }); + message.success('Updated matching'); + } + }; + + private handleSetBounty = async () => { + if (store.proposalDetail) { + FeedbackModal.open({ + title: 'Set bounty?', + content: + 'Set the bounty for this proposal. The bounty will count towards the funding goal.', + type: 'input', + inputProps: { + addonBefore: 'Amount', + addonAfter: 'ZEC', + placeholder: '1.5', + }, + okText: 'Set bounty', + onOk: async contributionBounty => { + await store.updateProposalDetail({ contributionBounty }); + message.success('Updated bounty'); + }, + }); } }; diff --git a/admin/src/store.ts b/admin/src/store.ts index 893e95a8..b562f7f9 100644 --- a/admin/src/store.ts +++ b/admin/src/store.ts @@ -249,6 +249,8 @@ const app = store({ proposalDetailApproving: false, proposalDetailMarkingMilestonePaid: false, proposalDetailCanceling: false, + proposalDetailUpdating: false, + proposalDetailUpdated: false, comments: { page: createDefaultPageData('CREATED:DESC'), @@ -466,15 +468,19 @@ const app = store({ if (!app.proposalDetail) { return; } + app.proposalDetailUpdating = true; + app.proposalDetailUpdated = false; try { const res = await updateProposal({ ...updates, proposalId: app.proposalDetail.proposalId, }); app.updateProposalInStore(res); + app.proposalDetailUpdated = true; } catch (e) { handleApiError(e); } + app.proposalDetailUpdating = false; }, async deleteProposal(id: number) { diff --git a/admin/src/types.ts b/admin/src/types.ts index 22184c00..d458034a 100644 --- a/admin/src/types.ts +++ b/admin/src/types.ts @@ -111,6 +111,8 @@ export interface Proposal { funded: string; rejectReason: string; contributionMatching: number; + contributionBounty: string; + rfpOptIn: null | boolean; rfp?: RFP; arbiter: ProposalArbiter; } diff --git a/backend/grant/admin/views.py b/backend/grant/admin/views.py index 0a29ae68..91ae8aa9 100644 --- a/backend/grant/admin/views.py +++ b/backend/grant/admin/views.py @@ -158,9 +158,9 @@ def stats(): .filter(ProposalContribution.status == ContributionStatus.CONFIRMED) \ .join(Proposal) \ .filter(or_( - Proposal.stage == ProposalStage.FAILED, - Proposal.stage == ProposalStage.CANCELED, - )) \ + Proposal.stage == ProposalStage.FAILED, + Proposal.stage == ProposalStage.CANCELED, + )) \ .join(ProposalContribution.user) \ .join(UserSettings) \ .filter(UserSettings.refund_address != None) \ @@ -312,9 +312,9 @@ def set_arbiter(proposal_id, user_id): db.session.commit() return { - 'proposal': proposal_schema.dump(proposal), - 'user': admin_user_schema.dump(user) - }, 200 + 'proposal': proposal_schema.dump(proposal), + 'user': admin_user_schema.dump(user) + }, 200 # PROPOSALS @@ -353,10 +353,11 @@ def delete_proposal(id): @blueprint.route('/proposals/', methods=['PUT']) @body({ - "contributionMatching": fields.Int(required=False, missing=None) + "contributionMatching": fields.Int(required=False, missing=None), + "contributionBounty": fields.Str(required=False, missing=None) }) @admin.admin_auth_required -def update_proposal(id, contribution_matching): +def update_proposal(id, contribution_matching, contribution_bounty): proposal = Proposal.query.filter(Proposal.id == id).first() if not proposal: return {"message": f"Could not find proposal with id {id}"}, 404 @@ -364,6 +365,9 @@ def update_proposal(id, contribution_matching): if contribution_matching is not None: proposal.set_contribution_matching(contribution_matching) + if contribution_bounty is not None: + proposal.set_contribution_bounty(contribution_bounty) + db.session.add(proposal) db.session.commit() @@ -495,8 +499,9 @@ def get_rfp(rfp_id): "title": fields.Str(required=True), "brief": fields.Str(required=True), "content": fields.Str(required=True), + "status": fields.Str(required=True), "category": fields.Str(required=True, validate=validate.OneOf(choices=Category.list())), - "bounty": fields.Str(required=False, missing=""), + "bounty": fields.Str(required=False, allow_none=True, missing=None), "matching": fields.Bool(required=False, default=False, missing=False), "dateCloses": fields.Int(required=False, missing=None), "status": fields.Str(required=True, validate=validate.OneOf(choices=RFPStatus.list())), @@ -513,7 +518,7 @@ def update_rfp(rfp_id, title, brief, content, category, bounty, matching, date_c rfp.content = content rfp.category = category rfp.matching = matching - rfp.bounty = bounty if bounty and bounty != "" else None + rfp.bounty = bounty rfp.date_closes = datetime.fromtimestamp(date_closes) if date_closes else None # Update timestamps if status changed @@ -607,7 +612,8 @@ def get_contribution(contribution_id): # TODO guard status "status": fields.Str(required=False, missing=None), "amount": fields.Str(required=False, missing=None), - "txId": fields.Str(required=False, missing=None) + "txId": fields.Str(required=False, missing=None), + "refundTxId": fields.Str(required=False, allow_none=True, missing=None), }) @admin.admin_auth_required def edit_contribution(contribution_id, proposal_id, user_id, status, amount, tx_id, refund_tx_id): diff --git a/backend/grant/proposal/models.py b/backend/grant/proposal/models.py index 8636e55f..ffdb9160 100644 --- a/backend/grant/proposal/models.py +++ b/backend/grant/proposal/models.py @@ -227,6 +227,8 @@ class Proposal(db.Model): payout_address = db.Column(db.String(255), nullable=False) deadline_duration = db.Column(db.Integer(), nullable=False) contribution_matching = db.Column(db.Float(), nullable=False, default=0, server_default=db.text("0")) + contribution_bounty = db.Column(db.String(255), nullable=False, default='0', server_default=db.text("'0'")) + rfp_opt_in = db.Column(db.Boolean(), nullable=True) contributed = db.column_property() # Relations @@ -342,6 +344,16 @@ class Proposal(db.Model): self.deadline_duration = deadline_duration Proposal.validate(vars(self)) + def update_rfp_opt_in(self, opt_in: bool): + self.rfp_opt_in = opt_in + # add/remove matching and/or bounty values from RFP + if opt_in and self.rfp: + self.set_contribution_matching(1 if self.rfp.matching else 0) + self.set_contribution_bounty(self.rfp.bounty or '0') + else: + self.set_contribution_matching(0) + self.set_contribution_bounty('0') + def create_contribution(self, amount, user_id: int = None, staking: bool = False): contribution = ProposalContribution( proposal_id=self.id, @@ -460,6 +472,16 @@ class Proposal(db.Model): # check the first step, if immediate payout bump it to accepted self.current_milestone.accept_immediate() + def set_contribution_bounty(self, bounty: str): + # do not allow changes on funded/WIP proposals + if self.is_funded: + raise ValidationException("Cannot change contribution bounty on fully-funded proposal") + # wrap in Decimal so it throws for non-decimal strings + self.contribution_bounty = str(Decimal(bounty)) + db.session.add(self) + db.session.flush() + self.set_funded_when_ready() + def set_contribution_matching(self, matching: float): # do not allow on funded/WIP proposals if self.is_funded: @@ -474,7 +496,6 @@ class Proposal(db.Model): raise ValidationException("Bad value for contribution_matching, must be 1 or 0") def cancel(self): - print(self.status) if self.status != ProposalStatus.LIVE: raise ValidationException("Cannot cancel a proposal until it's live") @@ -508,9 +529,9 @@ class Proposal(db.Model): target = Decimal(self.target) # apply matching multiplier funded = Decimal(self.contributed) * Decimal(1 + self.contribution_matching) - # apply bounty, if available - if self.rfp and self.rfp.bounty and self.rfp.bounty != "": - funded = funded + Decimal(self.rfp.bounty) + # apply bounty + if self.contribution_bounty: + funded = funded + Decimal(self.contribution_bounty) # if funded > target, just set as target if funded > target: return str(target) @@ -578,8 +599,10 @@ class ProposalSchema(ma.Schema): "payout_address", "deadline_duration", "contribution_matching", + "contribution_bounty", "invites", "rfp", + "rfp_opt_in", "arbiter" ) @@ -737,7 +760,7 @@ class ProposalContributionSchema(ma.Schema): return { 'transparent': addresses['transparent'], } - + def get_is_anonymous(self, obj): return not obj.user_id diff --git a/backend/grant/proposal/views.py b/backend/grant/proposal/views.py index 12164449..de00e6e3 100644 --- a/backend/grant/proposal/views.py +++ b/backend/grant/proposal/views.py @@ -187,8 +187,6 @@ def make_proposal_draft(rfp_id): if not rfp: return {"message": "The request this proposal was made for doesn’t exist"}, 400 proposal.category = rfp.category - if rfp.matching: - proposal.contribution_matching = 1.0 rfp.proposals.append(proposal) db.session.add(rfp) @@ -226,14 +224,20 @@ def get_proposal_drafts(): "payoutAddress": fields.Str(required=True), "deadlineDuration": fields.Int(required=True), "milestones": fields.List(fields.Dict(), required=True), + "rfpOptIn": fields.Bool(required=False, missing=None) }) -def update_proposal(milestones, proposal_id, **kwargs): +def update_proposal(milestones, proposal_id, rfp_opt_in, **kwargs): # Update the base proposal fields try: g.current_proposal.update(**kwargs) except ValidationException as e: return {"message": "{}".format(str(e))}, 400 db.session.add(g.current_proposal) + + # twiddle rfp opt-in (modifies proposal matching and/or bounty) + if rfp_opt_in is not None: + g.current_proposal.update_rfp_opt_in(rfp_opt_in) + # Delete & re-add milestones [db.session.delete(x) for x in g.current_proposal.milestones] if milestones: @@ -258,6 +262,9 @@ def update_proposal(milestones, proposal_id, **kwargs): @requires_team_member_auth def unlink_proposal_from_rfp(proposal_id): g.current_proposal.rfp_id = None + # this will zero matching and bounty + g.current_proposal.update_rfp_opt_in(False) + g.current_proposal.rfp_opt_in = None db.session.add(g.current_proposal) db.session.commit() return proposal_schema.dump(g.current_proposal), 200 diff --git a/backend/grant/rfp/models.py b/backend/grant/rfp/models.py index 02e0d2be..c9159d43 100644 --- a/backend/grant/rfp/models.py +++ b/backend/grant/rfp/models.py @@ -1,5 +1,7 @@ from datetime import datetime +from decimal import Decimal from grant.extensions import ma, db +from sqlalchemy.ext.hybrid import hybrid_property from grant.utils.enums import RFPStatus from grant.utils.misc import dt_to_unix, gen_random_id from grant.utils.enums import Category @@ -17,7 +19,7 @@ class RFP(db.Model): category = db.Column(db.String(255), nullable=False) status = db.Column(db.String(255), nullable=False) matching = db.Column(db.Boolean, default=False, nullable=False) - bounty = db.Column(db.String(255), nullable=True) + _bounty = db.Column("bounty", db.String(255), nullable=True) date_closes = db.Column(db.DateTime, nullable=True) date_opened = db.Column(db.DateTime, nullable=True) date_closed = db.Column(db.DateTime, nullable=True) @@ -36,6 +38,17 @@ class RFP(db.Model): cascade="all, delete-orphan", ) + @hybrid_property + def bounty(self): + return self._bounty + + @bounty.setter + def bounty(self, bounty: str): + if bounty and Decimal(bounty) > 0: + self._bounty = bounty + else: + self._bounty = None + def __init__( self, title: str, @@ -102,6 +115,7 @@ class RFPSchema(ma.Schema): def get_date_closed(self, obj): return dt_to_unix(obj.date_closed) if obj.date_closed else None + rfp_schema = RFPSchema() rfps_schema = RFPSchema(many=True) @@ -141,15 +155,16 @@ class AdminRFPSchema(ma.Schema): def get_date_created(self, obj): return dt_to_unix(obj.date_created) - + def get_date_closes(self, obj): return dt_to_unix(obj.date_closes) if obj.date_closes else None - + def get_date_opened(self, obj): return dt_to_unix(obj.date_opened) if obj.date_opened else None - + def get_date_closed(self, obj): return dt_to_unix(obj.date_closes) if obj.date_closes else None + admin_rfp_schema = AdminRFPSchema() admin_rfps_schema = AdminRFPSchema(many=True) diff --git a/backend/migrations/versions/13365ffe910e_.py b/backend/migrations/versions/13365ffe910e_.py new file mode 100644 index 00000000..3a031b05 --- /dev/null +++ b/backend/migrations/versions/13365ffe910e_.py @@ -0,0 +1,31 @@ +"""add proposal contribution_bounty & rfp_opt_in + +Revision ID: 13365ffe910e +Revises: 332a15eba9d8 +Create Date: 2019-02-28 19:41:42.215923 + +""" +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision = '13365ffe910e' +down_revision = '332a15eba9d8' +branch_labels = None +depends_on = None + + +def upgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('proposal', sa.Column('contribution_bounty', sa.String( + length=255), server_default=sa.text("'0'"), nullable=False)) + op.add_column('proposal', sa.Column('rfp_opt_in', sa.Boolean(), nullable=True)) + # ### end Alembic commands ### + + +def downgrade(): + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('proposal', 'rfp_opt_in') + op.drop_column('proposal', 'contribution_bounty') + # ### end Alembic commands ### diff --git a/frontend/client/api/api.ts b/frontend/client/api/api.ts index 37641f65..0a52a027 100644 --- a/frontend/client/api/api.ts +++ b/frontend/client/api/api.ts @@ -213,7 +213,11 @@ export function deleteProposalDraft(proposalId: number): Promise { export function putProposal(proposal: ProposalDraft): Promise<{ data: ProposalDraft }> { // Exclude some keys - const { proposalId, stage, dateCreated, team, ...rest } = proposal; + const { proposalId, stage, dateCreated, team, rfpOptIn, ...rest } = proposal; + // add rfpOptIn if it is not null + if (rfpOptIn !== null) { + (rest as any).rfpOptIn = rfpOptIn; + } return axios.put(`/api/v1/proposals/${proposal.proposalId}`, rest); } diff --git a/frontend/client/components/ContributionModal/PaymentInfo.tsx b/frontend/client/components/ContributionModal/PaymentInfo.tsx index 654958ae..b9d6ed0c 100644 --- a/frontend/client/components/ContributionModal/PaymentInfo.tsx +++ b/frontend/client/components/ContributionModal/PaymentInfo.tsx @@ -109,7 +109,7 @@ export default class PaymentInfo extends React.Component { text = ` Thank you for contributing! Just send using whichever method works best for you, and your contribution will show up anonymously once it's been confirmed. - ` + `; } else { text = ` Thank you for contributing! Just send using whichever method works best for @@ -120,9 +120,7 @@ export default class PaymentInfo extends React.Component { return (
-
- {text} -
+
{text}
{ description={ <> You are about to contribute anonymously. Your contribution will show up - without attribution, and even if you're logged in, will not - appear anywhere on your account after you close this modal. + without attribution, and even if you're logged in, will not appear anywhere + on your account after you close this modal.

In the case of a refund, your contribution will be treated as a donation to the Zcash Foundation instead.

- If you would like to have your contribution attached to an account, you - can close this modal, make sure you're logged in, and don't check the + If you would like to have your contribution attached to an account, you can + close this modal, make sure you're logged in, and don't check the "Contribute anonymously" checkbox. } @@ -116,15 +116,14 @@ export default class ContributionModal extends React.Component { description={ <> Your transaction should be confirmed in about 20 minutes.{' '} - {isAnonymous - ? 'Once it’s confirmed, it’ll show up in the contributions tab.' - : ( - <> - You can keep an eye on it at the{' '} - funded tab on your profile. - - ) - } + {isAnonymous ? ( + 'Once it’s confirmed, it’ll show up in the contributions tab.' + ) : ( + <> + You can keep an eye on it at the{' '} + funded tab on your profile. + + )} } style={{ width: '90%' }} diff --git a/frontend/client/components/CreateFlow/Basics.tsx b/frontend/client/components/CreateFlow/Basics.tsx index a602c502..ab064d83 100644 --- a/frontend/client/components/CreateFlow/Basics.tsx +++ b/frontend/client/components/CreateFlow/Basics.tsx @@ -1,7 +1,9 @@ import React from 'react'; import { connect } from 'react-redux'; -import { Input, Form, Icon, Select, Alert, Popconfirm, message } from 'antd'; +import BN from 'bn.js'; +import { Input, Form, Icon, Select, Alert, Popconfirm, message, Radio } from 'antd'; import { SelectValue } from 'antd/lib/select'; +import { RadioChangeEvent } from 'antd/lib/radio'; import { PROPOSAL_CATEGORY, CATEGORY_UI } from 'api/constants'; import { ProposalDraft, RFP } from 'types'; import { getCreateErrors } from 'modules/create/utils'; @@ -63,13 +65,17 @@ class CreateFlowBasics extends React.Component { render() { const { isUnlinkingProposalRFP } = this.props; - const { title, brief, category, target, rfp } = this.state; + const { title, brief, category, target, rfp, rfpOptIn } = this.state; const errors = getCreateErrors(this.state, true); + const rfpOptInRequired = + rfp && (rfp.matching || (rfp.bounty && new BN(rfp.bounty).gtn(0))); + return ( {rfp && ( { to do so. } - style={{ marginBottom: '2rem' }} showIcon /> )} + {rfpOptInRequired && ( + +
+ This RFP offers either a bounty or matching. This will require ZFGrants + to fulfill{' '} + + KYC + {' '} + due dilligence. In the event your proposal is successful, you will need + to provide identifying information to ZFGrants. + + + Yes, I am willing to provide KYC information + + + No, I do not wish to provide KYC information and understand I + will not receive any matching or bounty funds from ZFGrants + + +
+ + } + /> + )} + { }); }; + private handleRfpOptIn = (e: RadioChangeEvent) => { + this.setState({ rfpOptIn: e.target.value }, () => { + this.props.updateForm(this.state); + }); + }; + private unlinkRfp = () => { this.props.unlinkProposalRFP(this.props.proposalId); }; diff --git a/frontend/client/components/CreateFlow/Payment.tsx b/frontend/client/components/CreateFlow/Payment.tsx index 8c91eed2..18bdfc3c 100644 --- a/frontend/client/components/CreateFlow/Payment.tsx +++ b/frontend/client/components/CreateFlow/Payment.tsx @@ -29,7 +29,9 @@ export default class CreateFlowPayment extends React.Component { render() { const { payoutAddress, deadlineDuration } = this.state; const errors = getCreateErrors(this.state, true); - const payoutHelp = errors.payoutAddress || ` + const payoutHelp = + errors.payoutAddress || + ` This must be a Sapling Z address `; diff --git a/frontend/client/components/CreateFlow/Review.tsx b/frontend/client/components/CreateFlow/Review.tsx index 3db4c4de..defe4417 100644 --- a/frontend/client/components/CreateFlow/Review.tsx +++ b/frontend/client/components/CreateFlow/Review.tsx @@ -25,6 +25,7 @@ interface Field { key: KeyOfForm; content: React.ReactNode; error: string | Falsy; + isHide?: boolean; } interface Section { @@ -48,6 +49,12 @@ class CreateReview extends React.Component { content:

{form.title}

, error: errors.title, }, + { + key: 'rfpOptIn', + content:
{form.rfpOptIn ? 'Accepted' : 'Declined'}
, + error: errors.rfpOptIn, + isHide: !form.rfp || (form.rfp && !form.rfp.matching && !form.rfp.bounty), + }, { key: 'brief', content: form.brief, @@ -126,21 +133,26 @@ class CreateReview extends React.Component {
{sections.map(s => (
- {s.fields.map(f => ( -
-
- {FIELD_NAME_MAP[f.key]} - {f.error &&
{f.error}
} -
-
- {this.isEmpty(form[f.key]) ? ( -
N/A
- ) : ( - f.content - )} -
-
- ))} + {s.fields.map( + f => + !f.isHide && ( +
+
+ {FIELD_NAME_MAP[f.key]} + {f.error && ( +
{f.error}
+ )} +
+
+ {this.isEmpty(form[f.key]) ? ( +
N/A
+ ) : ( + f.content + )} +
+
+ ), + )}
@@ -163,6 +175,9 @@ class CreateReview extends React.Component { }; private isEmpty(value: any) { + if (typeof value === 'boolean') { + return false; // defined booleans are never empty + } return !value || value.length === 0; } } diff --git a/frontend/client/components/CreateFlow/index.less b/frontend/client/components/CreateFlow/index.less index fb1476db..3ffec37e 100644 --- a/frontend/client/components/CreateFlow/index.less +++ b/frontend/client/components/CreateFlow/index.less @@ -25,7 +25,7 @@ margin-bottom: 0.5rem; text-align: center; } - + &-subtitle { font-size: 1.4rem; margin-bottom: 0; @@ -79,7 +79,7 @@ &.is-primary { background: @primary-color; - color: #FFF; + color: #fff; border: none; } @@ -111,6 +111,25 @@ } } + &-rfpAlert { + margin-bottom: 2rem; + + .ant-radio-group { + display: block; + margin: 1.5rem 0 0 1.5rem; + + .ant-radio-wrapper { + display: flex; + margin: 1rem 0; + + span.ant-radio + * { + white-space: normal; + margin-top: -0.2rem; + } + } + } + } + &-draftNotification { position: fixed; bottom: 8rem; @@ -127,4 +146,4 @@ left: 50%; transform: translate(-50%, -50%); } -} \ No newline at end of file +} diff --git a/frontend/client/components/Header/Drawer.tsx b/frontend/client/components/Header/Drawer.tsx index f74e5e9f..31114393 100644 --- a/frontend/client/components/Header/Drawer.tsx +++ b/frontend/client/components/Header/Drawer.tsx @@ -55,28 +55,26 @@ class HeaderDrawer extends React.Component {
Navigation
- {user ? ( - [ - - Profile - , - - Settings - , - - Sign out - , - ] - ) : ( - [ - - Sign in - , - - Create account - - ] - )} + {user + ? [ + + Profile + , + + Settings + , + + Sign out + , + ] + : [ + + Sign in + , + + Create account + , + ]} diff --git a/frontend/client/components/Home/Actions.tsx b/frontend/client/components/Home/Actions.tsx index 54dab3ef..fab1df92 100644 --- a/frontend/client/components/Home/Actions.tsx +++ b/frontend/client/components/Home/Actions.tsx @@ -11,7 +11,7 @@ const HomeActions: React.SFC = ({ t }) => ( {t('home.actions.proposals')} - {t('home.actions.requests')} + {t('home.actions.requests')}
diff --git a/frontend/client/components/Home/Guide.tsx b/frontend/client/components/Home/Guide.tsx index fba212a9..b22d3425 100644 --- a/frontend/client/components/Home/Guide.tsx +++ b/frontend/client/components/Home/Guide.tsx @@ -7,28 +7,31 @@ import CompleteIcon from 'static/images/guide-complete.svg'; import './Guide.less'; const HomeGuide: React.SFC = ({ t }) => { - const items = [{ - text: t('home.guide.submit'), - icon: , - }, { - text: t('home.guide.review'), - icon: , - }, { - text: t('home.guide.community'), - icon: , - }, { - text: t('home.guide.complete'), - icon: , - }]; + const items = [ + { + text: t('home.guide.submit'), + icon: , + }, + { + text: t('home.guide.review'), + icon: , + }, + { + text: t('home.guide.community'), + icon: , + }, + { + text: t('home.guide.complete'), + icon: , + }, + ]; return (
- + -

- {t('home.guide.title')} -

+

{t('home.guide.title')}

{items.map((item, idx) => (
diff --git a/frontend/client/components/Home/Intro.tsx b/frontend/client/components/Home/Intro.tsx index b6cf675e..37d96bf4 100644 --- a/frontend/client/components/Home/Intro.tsx +++ b/frontend/client/components/Home/Intro.tsx @@ -38,6 +38,6 @@ const HomeIntro: React.SFC = ({ t, authUser }) => (
); -export default connect( - state => ({ authUser: state.auth.user }), -)(withNamespaces()(HomeIntro)); +export default connect(state => ({ + authUser: state.auth.user, +}))(withNamespaces()(HomeIntro)); diff --git a/frontend/client/components/Home/Requests.tsx b/frontend/client/components/Home/Requests.tsx index d99bca24..e3d2ea45 100644 --- a/frontend/client/components/Home/Requests.tsx +++ b/frontend/client/components/Home/Requests.tsx @@ -67,25 +67,21 @@ class HomeRequests extends React.Component {
-

- {t('home.requests.title')} -

+

{t('home.requests.title')}

- {t('home.requests.description').split('\n').map((s: string, idx: number) => -

{s}

- )} + {t('home.requests.description') + .split('\n') + .map((s: string, idx: number) => ( +

{s}

+ ))}
-
- {content} -
+
{content}
); } } - - export default connect( state => ({ rfps: state.rfps.rfps, diff --git a/frontend/client/components/Proposal/CampaignBlock/index.tsx b/frontend/client/components/Proposal/CampaignBlock/index.tsx index 9593b871..7b32ba81 100644 --- a/frontend/client/components/Proposal/CampaignBlock/index.tsx +++ b/frontend/client/components/Proposal/CampaignBlock/index.tsx @@ -66,10 +66,10 @@ export class ProposalCampaignBlock extends React.Component { // Get bounty from RFP. If it exceeds proposal target, show bounty as full amount let bounty; - if (proposal.rfp && proposal.rfp.bounty) { - bounty = proposal.rfp.bounty.gt(proposal.target) + if (proposal.contributionBounty && proposal.contributionBounty.gtn(0)) { + bounty = proposal.contributionBounty.gt(proposal.target) ? proposal.target - : proposal.rfp.bounty; + : proposal.contributionBounty; } content = ( diff --git a/frontend/client/components/UserRow/index.tsx b/frontend/client/components/UserRow/index.tsx index b3bcb84e..26dac054 100644 --- a/frontend/client/components/UserRow/index.tsx +++ b/frontend/client/components/UserRow/index.tsx @@ -9,9 +9,11 @@ interface Props { extra?: React.ReactNode; } -const Wrap = ({ user, children }: { user: User, children: React.ReactNode }) => { +const Wrap = ({ user, children }: { user: User; children: React.ReactNode }) => { if (user.userid) { - return ; + return ( + + ); } else { return
; } @@ -26,11 +28,7 @@ const UserRow = ({ user, extra }: Props) => (
{user.displayName}

{user.title}

- {extra && ( -
- {extra} -
- )} + {extra &&
{extra}
} ); diff --git a/frontend/client/index.tsx b/frontend/client/index.tsx index 02522932..7462fa61 100644 --- a/frontend/client/index.tsx +++ b/frontend/client/index.tsx @@ -25,7 +25,6 @@ const i18nLanguage = window && (window as any).__PRELOADED_I18N__; i18n.changeLanguage(i18nLanguage.locale); i18n.addResourceBundle(i18nLanguage.locale, 'common', i18nLanguage.resources, true); - const App = hot(module)(() => ( diff --git a/frontend/client/modules/create/utils.ts b/frontend/client/modules/create/utils.ts index 0c41fddc..c1061d59 100644 --- a/frontend/client/modules/create/utils.ts +++ b/frontend/client/modules/create/utils.ts @@ -6,7 +6,12 @@ import { PROPOSAL_ARBITER_STATUS, } from 'types'; import { User } from 'types'; -import { getAmountError, isValidSaplingAddress, isValidTAddress, isValidSproutAddress } from 'utils/validators'; +import { + getAmountError, + isValidSaplingAddress, + isValidTAddress, + isValidSproutAddress, +} from 'utils/validators'; import { Zat, toZat } from 'utils/units'; import { ONE_DAY } from 'utils/time'; import { PROPOSAL_CATEGORY, PROPOSAL_STAGE } from 'api/constants'; @@ -18,6 +23,7 @@ import { export const TARGET_ZEC_LIMIT = 1000; interface CreateFormErrors { + rfpOptIn?: string; title?: string; brief?: string; category?: string; @@ -31,6 +37,7 @@ interface CreateFormErrors { export type KeyOfForm = keyof CreateFormErrors; export const FIELD_NAME_MAP: { [key in KeyOfForm]: string } = { + rfpOptIn: 'RFP KYC', title: 'Title', brief: 'Brief', category: 'Category', @@ -57,7 +64,7 @@ export function getCreateErrors( skipRequired?: boolean, ): CreateFormErrors { const errors: CreateFormErrors = {}; - const { title, team, milestones, target, payoutAddress } = form; + const { title, team, milestones, target, payoutAddress, rfp, rfpOptIn } = form; // Required fields with no extra validation if (!skipRequired) { @@ -75,6 +82,11 @@ export function getCreateErrors( } } + // RFP opt-in + if (rfp && (rfp.bounty || rfp.matching) && rfpOptIn === null) { + errors.rfpOptIn = 'Please accept or decline KYC'; + } + // Title if (title && title.length > 60) { errors.title = 'Title can only be 60 characters maximum'; @@ -212,6 +224,7 @@ export function makeProposalPreviewFromDraft(draft: ProposalDraft): ProposalDeta target: toZat(draft.target), funded: Zat('0'), contributionMatching: 0, + contributionBounty: Zat('0'), percentFunded: 0, stage: PROPOSAL_STAGE.PREVIEW, category: draft.category || PROPOSAL_CATEGORY.CORE_DEV, diff --git a/frontend/client/utils/api.ts b/frontend/client/utils/api.ts index 7caa30fa..02da03fb 100644 --- a/frontend/client/utils/api.ts +++ b/frontend/client/utils/api.ts @@ -86,6 +86,7 @@ export function formatProposalFromGet(p: any): Proposal { proposal.proposalUrlId = generateSlugUrl(proposal.proposalId, proposal.title); proposal.target = toZat(p.target); proposal.funded = toZat(p.funded); + proposal.contributionBounty = toZat(p.contributionBounty); proposal.percentFunded = proposal.target.isZero() ? 0 : proposal.funded.div(proposal.target.divn(100)).toNumber(); @@ -145,6 +146,8 @@ export function massageSerializedState(state: AppState) { (state.proposal.detail.funded as any) as string, 16, ); + state.proposal.detail.contributionBounty = new BN((state.proposal.detail + .contributionBounty as any) as string); if (state.proposal.detail.rfp && state.proposal.detail.rfp.bounty) { state.proposal.detail.rfp.bounty = new BN( (state.proposal.detail.rfp.bounty as any) as string, @@ -157,6 +160,7 @@ export function massageSerializedState(state: AppState) { ...p, target: new BN((p.target as any) as string, 16), funded: new BN((p.funded as any) as string, 16), + contributionBounty: new BN((p.contributionMatching as any) as string, 16), milestones: p.milestones.map(m => ({ ...m, amount: new BN((m.amount as any) as string, 16), diff --git a/frontend/client/utils/constants.ts b/frontend/client/utils/constants.ts index e5271f27..cd260add 100644 --- a/frontend/client/utils/constants.ts +++ b/frontend/client/utils/constants.ts @@ -1,5 +1,7 @@ export const DONATION = { ZCASH_TRANSPARENT: 't1aib2cbwPVrFfrjGGkhWD67imdBet1xDTr', - ZCASH_SPROUT: 'zcWGwZU7FyUgpdrWGkeFqCEnvhLRDAVuf2ZbhW4vzNMTTR6VUgfiBGkiNbkC4e38QaPtS13RKZCriqN9VcyyKNRRQxbgnen', - ZCASH_SAPLING: 'zs15el0hzs4w60ggfy6kq4p3zttjrl00mfq7yxfwsjqpz9d7hptdtkltzlcqar994jg2ju3j9k85zk', + ZCASH_SPROUT: + 'zcWGwZU7FyUgpdrWGkeFqCEnvhLRDAVuf2ZbhW4vzNMTTR6VUgfiBGkiNbkC4e38QaPtS13RKZCriqN9VcyyKNRRQxbgnen', + ZCASH_SAPLING: + 'zs15el0hzs4w60ggfy6kq4p3zttjrl00mfq7yxfwsjqpz9d7hptdtkltzlcqar994jg2ju3j9k85zk', }; diff --git a/frontend/client/utils/validators.ts b/frontend/client/utils/validators.ts index b7cc434f..e7cd7957 100644 --- a/frontend/client/utils/validators.ts +++ b/frontend/client/utils/validators.ts @@ -17,7 +17,6 @@ export function isValidEmail(email: string): boolean { return /\S+@\S+\.\S+/.test(email); } - // Uses simple regex to validate addresses, doesn't check checksum or network export function isValidTAddress(address: string): boolean { if (/^t[a-zA-Z0-9]{34}$/.test(address)) { diff --git a/frontend/stories/props.tsx b/frontend/stories/props.tsx index 9c489c07..2f0fcf18 100644 --- a/frontend/stories/props.tsx +++ b/frontend/stories/props.tsx @@ -155,6 +155,7 @@ export function generateProposal({ funded: fundedBn, percentFunded, contributionMatching: 0, + contributionBounty: new BN(0), title: 'Crowdfund Title', brief: 'A cool test crowdfund', content: 'body', diff --git a/frontend/types/proposal.ts b/frontend/types/proposal.ts index 143d9033..33548aa9 100644 --- a/frontend/types/proposal.ts +++ b/frontend/types/proposal.ts @@ -46,6 +46,7 @@ export interface ProposalDraft { status: STATUS; isStaked: boolean; rfp?: RFP; + rfpOptIn?: boolean; } export interface Proposal extends Omit { @@ -55,6 +56,7 @@ export interface Proposal extends Omit { funded: Zat; percentFunded: number; contributionMatching: number; + contributionBounty: Zat; milestones: ProposalMilestone[]; currentMilestone?: ProposalMilestone; datePublished: number | null; From 1973db13306ffec3d48288e04b403198d97cdb9e Mon Sep 17 00:00:00 2001 From: Will O'Beirne Date: Wed, 6 Mar 2019 15:33:09 -0500 Subject: [PATCH 23/72] Notify user if contribution expires. --- admin/src/components/Emails/emails.ts | 5 +++ backend/grant/admin/example_emails.py | 7 ++++ backend/grant/email/send.py | 13 ++++++ backend/grant/proposal/models.py | 5 ++- backend/grant/task/jobs.py | 42 ++++++++++++++++++- .../emails/contribution_expired.html | 22 ++++++++++ .../templates/emails/contribution_expired.txt | 13 ++++++ 7 files changed, 105 insertions(+), 2 deletions(-) create mode 100644 backend/grant/templates/emails/contribution_expired.html create mode 100644 backend/grant/templates/emails/contribution_expired.txt diff --git a/admin/src/components/Emails/emails.ts b/admin/src/components/Emails/emails.ts index 80752ea9..fdb2e2a7 100644 --- a/admin/src/components/Emails/emails.ts +++ b/admin/src/components/Emails/emails.ts @@ -92,6 +92,11 @@ export default [ title: 'Contribution proposal canceled', description: 'Sent to contributors when an admin cancels the proposal after funding', }, + { + id: 'contribution_expired', + title: 'Contribution expired', + description: 'Sent 24 hours after a contribution is made with no confirmation', + }, { id: 'comment_reply', title: 'Comment reply', diff --git a/backend/grant/admin/example_emails.py b/backend/grant/admin/example_emails.py index 508a3072..076d034a 100644 --- a/backend/grant/admin/example_emails.py +++ b/backend/grant/admin/example_emails.py @@ -126,6 +126,13 @@ example_email_args = { 'refund_address': 'ztqdzvnK2SE27FCWg69EdissCBn7twnfd1XWLrftiZaT4rSFCkp7eQGQDSWXBF43sM5cyA4c8qyVjBP9Cf4zTcFJxf71ve8', 'account_settings_url': 'http://accountsettingsurl.com/', }, + 'contribution_expired': { + 'proposal': proposal, + 'contribution': contribution, + 'contact_url': 'http://somecontacturl.com', + 'profile_url': 'http://someprofile.com', + 'proposal_url': 'http://someproposal.com', + }, 'comment_reply': { 'author': user, 'proposal': proposal, diff --git a/backend/grant/email/send.py b/backend/grant/email/send.py index 1fc7fbf2..d56b79ff 100644 --- a/backend/grant/email/send.py +++ b/backend/grant/email/send.py @@ -199,6 +199,18 @@ def contribution_proposal_canceled(email_args): } +def contribution_expired(email_args): + return { + 'subject': 'Your contribution expired', + 'title': 'Contribution expired', + 'preview': 'Your {} ZEC contribution to {} could not be confirmed, and has expired'.format( + email_args['contribution'].amount, + email_args['proposal'].title, + ), + 'subscription': EmailSubscription.FUNDED_PROPOSAL_CONTRIBUTION, + } + + def comment_reply(email_args): return { 'subject': 'New reply from {}'.format(email_args['author'].display_name), @@ -282,6 +294,7 @@ get_info_lookup = { 'contribution_refunded': contribution_refunded, 'contribution_proposal_failed': contribution_proposal_failed, 'contribution_proposal_canceled': contribution_proposal_canceled, + 'contribution_expired': contribution_expired, 'comment_reply': comment_reply, 'proposal_arbiter': proposal_arbiter, 'milestone_request': milestone_request, diff --git a/backend/grant/proposal/models.py b/backend/grant/proposal/models.py index e1b43e5d..a0062812 100644 --- a/backend/grant/proposal/models.py +++ b/backend/grant/proposal/models.py @@ -21,6 +21,7 @@ from grant.utils.enums import ( MilestoneStage ) from grant.utils.stubs import anonymous_user +from grant.task.jobs import ContributionExpired proposal_team = db.Table( 'proposal_team', db.Model.metadata, @@ -351,7 +352,9 @@ class Proposal(db.Model): staking=staking, ) db.session.add(contribution) - db.session.commit() + db.session.flush() + task = ContributionExpired(contribution) + task.make_task() return contribution def get_staking_contribution(self, user_id: int): diff --git a/backend/grant/task/jobs.py b/backend/grant/task/jobs.py index 82a23a16..e7d17b89 100644 --- a/backend/grant/task/jobs.py +++ b/backend/grant/task/jobs.py @@ -2,7 +2,7 @@ from datetime import datetime, timedelta from grant.extensions import db from grant.email.send import send_email -from grant.utils.enums import ProposalStage +from grant.utils.enums import ProposalStage, ContributionStatus from grant.utils.misc import make_url @@ -86,8 +86,48 @@ class ProposalDeadline: 'account_settings_url': make_url('/profile/settings?tab=account') }) +class ContributionExpired: + JOB_TYPE = 3 + + def __init__(self, contribution): + self.contribution = contribution + + def blobify(self): + return { + "contribution_id": self.contribution.id, + } + + def make_task(self): + from .models import Task + task = Task( + job_type=self.JOB_TYPE, + blob=self.blobify(), + execute_after=self.contribution.date_created + timedelta(hours=24), + ) + db.session.add(task) + db.session.commit() + + @staticmethod + def process_task(task): + from grant.proposal.models import ProposalContribution + contribution = ProposalContribution.query.filter_by(id=task.blob["contribution_id"]).first() + + # If it's missing or not pending, noop out + if not contribution or contribution.status != ContributionStatus.PENDING: + return + + # Otherwise, inform the user (if not anonymous) + if contribution.user: + send_email(contribution.user.email_address, 'contribution_expired', { + 'contribution': contribution, + 'proposal': contribution.proposal, + 'contact_url': make_url('/contact'), + 'profile_url': make_url(f'/profile/{contribution.user.id}'), + 'proposal_url': make_url(f'/proposals/{contribution.proposal.id}'), + }) JOBS = { 1: ProposalReminder.process_task, 2: ProposalDeadline.process_task, + 3: ContributionExpired.process_task, } diff --git a/backend/grant/templates/emails/contribution_expired.html b/backend/grant/templates/emails/contribution_expired.html new file mode 100644 index 00000000..86c6b52f --- /dev/null +++ b/backend/grant/templates/emails/contribution_expired.html @@ -0,0 +1,22 @@ +

+ Your {{ args.contribution.amount }} ZEC contribution to + {{ args.proposal.title}} could not be confirmed on-chain, + and has expired. +

+ +

+ If you did not send the contribution: + You have nothing to worry about, you can simply delete the contribution from + your profile. Just make that sure you do + not send any money to the contribution address. If you'd still like to make a + contribution, you can start a new one on the + proposal page. +

+ +

+ If you're sure you sent the contribution: + Please contact our team using a method from the + contact page with details about the + contribution, such as a transaction ID or payment + disclosure from the transaction. +

diff --git a/backend/grant/templates/emails/contribution_expired.txt b/backend/grant/templates/emails/contribution_expired.txt new file mode 100644 index 00000000..4ceaae0e --- /dev/null +++ b/backend/grant/templates/emails/contribution_expired.txt @@ -0,0 +1,13 @@ +Your {{ args.contribution.amount }} ZEC contribution to "{{ args.proposal.title}}" +could not be confirmed on-chain, and has expired. + +If you did not send the contribution, you have nothing to worry about, you can +simply delete the contribution from your profile. Just make that sure you do not +send any money to the contribution address. If you'd still like to make a +contribution, you can start a new one on the proposal page. + +If you're sure you've sent the contribution, please contact our team using a method +from the link below with details about the contribution, such as a transaction ID or +payment disclosure from the transaction. + +Contact us at {{ args.contact_url }} \ No newline at end of file From a9ae24557803edeb28a66054c879fd3ed76136b1 Mon Sep 17 00:00:00 2001 From: Will O'Beirne Date: Wed, 6 Mar 2019 16:39:30 -0500 Subject: [PATCH 24/72] Dont add task for anon contributions --- backend/grant/proposal/models.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/backend/grant/proposal/models.py b/backend/grant/proposal/models.py index a0062812..41943a6c 100644 --- a/backend/grant/proposal/models.py +++ b/backend/grant/proposal/models.py @@ -353,8 +353,10 @@ class Proposal(db.Model): ) db.session.add(contribution) db.session.flush() - task = ContributionExpired(contribution) - task.make_task() + if user_id: + task = ContributionExpired(contribution) + task.make_task() + db.session.commit() return contribution def get_staking_contribution(self, user_id: int): From 312818d71a244e6ccddf9f6863b769def1a9c276 Mon Sep 17 00:00:00 2001 From: William O'Beirne Date: Wed, 6 Mar 2019 16:50:32 -0500 Subject: [PATCH 25/72] New footer (#300) * Finalize design and layout. * Change about page to redirect. --- frontend/client/components/Footer/index.tsx | 67 ++++-- frontend/client/components/Footer/style.less | 226 ++++++++++++++++-- frontend/client/pages/about.tsx | 8 +- .../static/images/grantio-logo-icon.svg | 8 + .../static/images/grantio-logo-name.svg | 5 + frontend/client/static/markdown/ABOUT.md | 93 ------- frontend/client/styles/variables.less | 2 +- 7 files changed, 270 insertions(+), 139 deletions(-) create mode 100644 frontend/client/static/images/grantio-logo-icon.svg create mode 100644 frontend/client/static/images/grantio-logo-name.svg delete mode 100644 frontend/client/static/markdown/ABOUT.md diff --git a/frontend/client/components/Footer/index.tsx b/frontend/client/components/Footer/index.tsx index 42e99e12..081d59c0 100644 --- a/frontend/client/components/Footer/index.tsx +++ b/frontend/client/components/Footer/index.tsx @@ -1,29 +1,58 @@ import React from 'react'; import { Link } from 'react-router-dom'; -import Logo from 'static/images/logo-name-light.svg'; +import { Icon } from 'antd'; +import ZFGrantsLogo from 'static/images/logo-name-light.svg'; +import GrantIoLogo from 'static/images/grantio-logo-name.svg'; import './style.less'; export default () => (
- - - -
- - about - - - contact - - - terms of service - - - privacy policy - - - code of conduct +
+
+ © {new Date().getFullYear()} Zcash Foundation +
+ + +
+ + +

+ ZF Grants is an open-source, community driven platform that helps + creators get funding to build a better Zcash. ZF Grants is owned + and operated by the Zcash Foundation. +

+
+ + About + + + Contact + + + Terms of Service + + + Privacy Policy + + + Code of Conduct + +
+
+
); diff --git a/frontend/client/components/Footer/style.less b/frontend/client/components/Footer/style.less index d836e22d..bc680427 100644 --- a/frontend/client/components/Footer/style.less +++ b/frontend/client/components/Footer/style.less @@ -1,47 +1,225 @@ @import '~styles/variables.less'; +@main-width: 620px; + .Footer { display: flex; - justify-content: center; + justify-content: space-between; align-items: center; - flex-direction: column; + padding: 0 2rem; color: #FFF; background: @secondary-color; - height: 160px; + height: 260px; - &-title { - margin-bottom: 1.25rem; - opacity: 0.8; - transition: transform 100ms ease, opacity 100ms ease; + @media @tablet-query { + height: auto; + padding: 2rem; + flex-wrap: wrap; + justify-content: center; + } - &:hover, - &:focus, - &:active { - opacity: 1; - transform: translateY(-1px); - color: inherit; + &-main { + max-width: @main-width; + padding: 0 1rem; + text-align: center; + + @media @tablet-query { + width: 100%; + max-width: none; + flex-shrink: 0; + order: 1; + padding-bottom: 2rem; + margin-bottom: 2rem; + border-bottom: 1px solid rgba(#FFF, 0.08); } - &-logo { - height: 1.6rem; + @media @mobile-query { + padding: 0 0 2rem; + } + + &-title { + margin-bottom: 1.25rem; + opacity: 0.8; + transition: transform 100ms ease, opacity 100ms ease; + + &:hover, + &:focus, + &:active { + opacity: 1; + transform: translateY(-1px); + color: inherit; + } + + &-logo { + height: 1.8rem; + margin-bottom: 1rem; + } + } + + &-about { + max-width: @main-width; + margin: 0 auto 1rem; + font-size: 0.85rem; + line-height: 1.4rem; + opacity: 0.8; + } + + &-links { + display: flex; + justify-content: center; + align-items: center; + + @media @tiny-query { + flex-direction: column; + } + + &-link { + font-size: 0.7rem; + padding: 0 1rem; + opacity: 0.6; + color: #FFF; + + @media @mobile-query { + padding: 0 0.6rem; + } + + @media @tiny-query { + margin-bottom: 0.5rem; + font-size: 0.9rem; + } + + &:hover { + opacity: 1; + color: #FFF; + } + } } } - &-links { - display: flex; - justify-content: center; - align-items: center; + &-social { + @media @tablet-query { + order: 3; + flex-grow: 1; + max-width: @main-width / 2; + } + + @media @tiny-query { + order: 2; + width: 100%; + flex-shrink: 0; + margin-bottom: 2rem; + } &-link { - font-size: 1rem; - padding: 0 1rem; + display: flex; + justify-content: flex-end; + align-items: center; + font-size: 0.8rem; + opacity: 0.35; + margin-bottom: 0.75rem; color: #FFF; - opacity: 0.8; - transition: opacity 100ms ease; + + @media @tablet-query { + justify-content: center; + } + + &:last-child { + margin: 0; + } + + .anticon { + display: flex; + justify-content: center; + align-items: center; + height: 1.6rem; + width: 1.6rem; + font-size: 1.1rem; + margin-left: 0.5rem; + text-align: center; + background: #FFF; + color: @secondary-color; + border-radius: 4px; + } &:hover { - color: inherit; opacity: 1; + color: #FFF; + + .anticon { + color: @secondary-color; + } + } + } + } + + &-attribution { + display: flex; + flex-direction: column; + justify-content: center; + align-items: flex-start; + + @media @tablet-query { + order: 2; + flex-grow: 1; + max-width: @main-width / 2; + align-items: center; + text-align: center; + } + + @media @tiny-query { + order: 3; + width: 100%; + flex-shrink: 0; + } + + &-copyright { + opacity: 0.6; + margin-bottom: 2rem; + color: #FFF; + font-size: 0.7rem; + + @media @tablet-query { + margin-bottom: 1rem; + } + } + + &-grantio { + display: block; + opacity: 0.35; + transition: opacity 100ms ease; + max-width: 100px; + + @media @mobile-query { + max-width: 80px; + } + + &:hover { + opacity: 0.8; + } + + &-prefix { + display: flex; + align-items: center; + color: #FFF; + font-size: 0.45rem; + margin-bottom: 0.3rem; + opacity: 0.8; + text-decoration: none; + text-transform: uppercase; + letter-spacing: 0.08rem; + + &-line { + flex: 1; + height: 1px; + background: linear-gradient(to right, rgba(#FFF, 1), rgba(#FFF, 0)); + margin-left: 0.5rem; + } + } + + &-logo { + opacity: 0.6; + width: 95%; + fill: #FFF; } } } diff --git a/frontend/client/pages/about.tsx b/frontend/client/pages/about.tsx index db4f1b0e..bb1c7cd2 100644 --- a/frontend/client/pages/about.tsx +++ b/frontend/client/pages/about.tsx @@ -1,7 +1,11 @@ import React from 'react'; import MarkdownPage from 'components/MarkdownPage'; -import ABOUT from 'static/markdown/ABOUT.md'; -const About = () => ; +const About = () => { + if (typeof window !== 'undefined') { + window.location.href = 'https://www.zfnd.org/about/'; + } + return ; +}; export default About; diff --git a/frontend/client/static/images/grantio-logo-icon.svg b/frontend/client/static/images/grantio-logo-icon.svg new file mode 100644 index 00000000..bf70d2d1 --- /dev/null +++ b/frontend/client/static/images/grantio-logo-icon.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/frontend/client/static/images/grantio-logo-name.svg b/frontend/client/static/images/grantio-logo-name.svg new file mode 100644 index 00000000..93002c8c --- /dev/null +++ b/frontend/client/static/images/grantio-logo-name.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/frontend/client/static/markdown/ABOUT.md b/frontend/client/static/markdown/ABOUT.md deleted file mode 100644 index f9fbb3bf..00000000 --- a/frontend/client/static/markdown/ABOUT.md +++ /dev/null @@ -1,93 +0,0 @@ -About the Zcash Foundation -========================== - -The Zcash Foundation is a [501(c)3 nonprofit](https://www.zfnd.org/about/incorporation-docs/) focused on financial privacy. We build and support privacy infrastructure for the public good, primarily serving users of the Zcash protocol and blockchain. - -- [Mission](https://www.zfnd.org/about/#mission) -- [Values](https://www.zfnd.org/about/#values) -- [Legal](https://www.zfnd.org/about/#legal) - -Our Mission ------------ - -Per our [501(c)3 application](https://www.zfnd.org/about/IncorporationDocs/Zcash%20Foundation%201023.pdf), the Zcash Foundation's mission is to be a public charity dedicated to building internet payment and privacy infrastructure for the public good, primarily serving the users of the Zcash protocol and blockchain. Our activities span three main pillars: - -### Community - -We support the transition of Zcash into a large and healthy community with diverse stakeholders, held accountable to its userbase in the broadest sense. Our activities include sponsoring a conference for developers, miners, and other community members, to help jump start the conversation. We also plan to organize mailing lists that give voice to groups of stakeholders that might not otherwise have them. - -Our own governance structure is designed especially with public transparency in mind (as specified in the [section on values](https://www.zfnd.org/about/#values)). So that the Foundation can be effectively held accountable to the community we serve. - -### Protocol and Governance - -The Zcash protocol and the open network it powers is valuable, privacy-protecting Internet infrastructure; it should be developed and maintained by an open and diverse community of experts and enthusiasts, and it should remain always a tool for financial privacy available to the general public. - -We will act as stewards of the Zcash protocol, providing leadership and supporting its continued maintenance and improvement. - -We will offer responsive leadership when changes are necessary on short notice, such as a hard fork to resist an attack. - -We will help coordinate on upgrades to the protocol. This can include organizing soft forks, organizing hard forks, and helping to conduct "ceremonies" such as parameter generation (should more of those be necessary). - -### Science - -Technical decisions are best supported by scientific activities, such as rigorous validation, empirical measurement, and continued innovation and refinement. We will therefore support these activities within our community. All cryptocurrencies, but especially Zcash, rely on cutting-edge contributions from the scientific research community, thus interaction with this community can also help the community stay healthy. - -We will encourage this scientific research and educate the public regarding the substance and benefits of these scientific developments. - -Our Values ----------- - -- *Transparency.* We are committed to being accountable to the community. Our policies, significant decisions and their rationale, and software shall all be publicly visible. We will strive to accurately and accessibly communicate the security properties of Zcash and of proposed changes. - -- *Inclusivity.* The Zcash ecosystem consists of many subcommunities, such as end-users, developers, miners, exchanges, traders and researchers, in many countries and cultures. They should all be given a voice within the Zcash community and the Foundation's deliberations. - -- *Privacy.* Privacy is an important human right, and the Foundation will work to enable users to control their own financial privacy. - -- *Decentralization.* We will strive to make the Zcash protocol and network decentralized, avoiding the placement of trust or granting of capabilities to any single party (assigned or emergent). Whenever current technology does not achieve perfect decentralization, we will seek to minimize, monitor and mitigate centralization. - -- *Humility.* The Foundation will not seek to place itself in a position of control over the Zcash community. The Foundation will not hold privileged information about Zcash transactions (beyond what is observable by anyone from the public blockchain or disclosures outside the blockchain), nor hold or create capabilities for acquiring such knowledge. - -- *Cognizance.* We strive to make well-informed and well-reasoned decisions, recognizing the vast body of knowledge offered by research and the experience of other cryptocurrencies. - -- *Innovation.* The Foundation shall support research and development to improve Zcash. It will strive to incorporate such progress into the Zcash protocol and software, while mindful of stability and security. - -- *Cooperation with other cryptocurrencies.* No "coin" is an island unto itself, and the success and acceptance of cryptocurrencies are intertwined. We will strive to collaborate with other cryptocurrencies' communities in areas of mutual interest. - -Legal ------ - -The Zcash Foundation is a nonprofit organization registered in the state of Delaware. - -Peruse our legal documents at the following links: - -- [Zcash Foundation 1023 (PDF)](https://www.zfnd.org/about/incorporation-docs/Zcash%20Foundation%201023.pdf) -- [Zcash Foundation Bylaws (PDF)](https://www.zfnd.org/about/incorporation-docs/Zcash%20Foundation%20Bylaws.pdf) -- [Bylaws Amendment 1](https://www.zfnd.org/about/incorporation-docs/bylaws_amendment_1) -- [Bylaws Amendment 2](https://www.zfnd.org/about/incorporation-docs/bylaws_amendment_2) -- [Zcash Foundation Certificate and Accepted Articles (PDF)](https://www.zfnd.org/about/incorporation-docs/Zcash%20Foundation%20Certificate%20and%20Accepted%20Articles.pdf) -- [Zcash Foundation IRS Non-Profit Determination Letter (PDF)](https://www.zfnd.org/about/incorporation-docs/Zcash%20Foundation%20IRS%20Non-Profit%20Determination%20Letter.pdf) -- [Board Minutes](https://www.zfnd.org/about/board-minutes/) - -
- -Team ----- - -### Board of Directors - -- [*Andrew Miller*](https://soc1024.com/) (Chair, President) - Assistant Professor in the Electrical and Computer Engineering department at the University of Illinois at Urbana-Champaign, and an associate director of the Initiative for Cryptocurrencies and Contracts. -- [*Peter Van Valkenburgh*](http://www.petervv.com/) (Secretary) - Director of Research at Coin Center, a nonprofit organization focused on research, education, and advocacy on the intersection of policy and cryptocurrencies. -- [*Matthew Green*](https://isi.jhu.edu/~mgreen/) - Associate Professor of Computer Science at Johns Hopkins University, and one of the authors of the original Zerocash research paper. -- [*Amber Baldet*](http://www.amberbaldet.com/) - CEO of Clovyr, former JPMorgan Blockchain Program Lead, and co-creator of a zero-knowledge settlement layer for enterprise Ethereum. -- [*Ian Miers*](https://cs.jhu.edu/~imiers/) - Postdoc studying computer security and applied cryptography at Cornell Tech, and one of the creators of Zcash. - -**Former members:** - -- [*Naval Ravikant*](https://angel.co/naval) - Founder and CEO of AngelList, and one of the venture capital investors behind Zcash. -- [*Yan Zhu*](https://diracdeltas.github.io/) - Senior Software Engineer at Brave and security researcher. - -### Employees - -- [*Josh Cincinnati*](https://twitter.com/acityinohio) - Executive Director of the Foundation, and an active member of the cryptocurrency community. -- [*Antonie Hodge*](https://twitter.com/antoniehodge) - Operations Director of the Zcash Foundation. Also serves as Operations Director at Coin Center. -- [*Sonya Mann*](https://twitter.com/sonyaellenmann) - Communications Manager for the Foundation and a former tech journalist. diff --git a/frontend/client/styles/variables.less b/frontend/client/styles/variables.less index e86e2c90..02d199ff 100644 --- a/frontend/client/styles/variables.less +++ b/frontend/client/styles/variables.less @@ -23,7 +23,7 @@ @max-content-width: 1440px; @thin-query: ~'(max-width: 1120px)'; -@tablet-query: ~'(max-width: 900px)'; +@tablet-query: ~'(max-width: 920px)'; @mobile-query: ~'(max-width: 600px)'; @tiny-query: ~'(max-width: 360px)'; From 9d51e325eca505185141dcbc988c9c8a13279682 Mon Sep 17 00:00:00 2001 From: Will O'Beirne Date: Wed, 6 Mar 2019 18:21:12 -0500 Subject: [PATCH 26/72] BNify RFPs --- frontend/client/utils/api.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/frontend/client/utils/api.ts b/frontend/client/utils/api.ts index 02da03fb..1d0b3e33 100644 --- a/frontend/client/utils/api.ts +++ b/frontend/client/utils/api.ts @@ -183,6 +183,13 @@ export function massageSerializedState(state: AppState) { return c; }); }); + // RFPs + state.rfps.rfps = state.rfps.rfps.map(rfp => { + if (rfp.bounty) { + rfp.bounty = new BN(rfp.bounty, 16); + } + return rfp; + }); return state; } From e9a052870c37081ecccf0aa81749aaea487afd68 Mon Sep 17 00:00:00 2001 From: Will O'Beirne Date: Wed, 6 Mar 2019 18:39:44 -0500 Subject: [PATCH 27/72] Change homepage splash image to background image that keeps its place before loading. --- frontend/client/components/Home/Intro.less | 10 +++++++--- frontend/client/components/Home/Intro.tsx | 7 ++++--- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/frontend/client/components/Home/Intro.less b/frontend/client/components/Home/Intro.less index 1082ccb3..e6bcea83 100644 --- a/frontend/client/components/Home/Intro.less +++ b/frontend/client/components/Home/Intro.less @@ -96,11 +96,15 @@ } &-illustration { + position: relative; + width: 100%; max-width: 640px; + background-size: contain; - img { - width: 100%; - height: auto; + &:after { + content: ''; + display: block; + padding-top: 86.3%; // Aspect ratio of img } @media @thin-query { diff --git a/frontend/client/components/Home/Intro.tsx b/frontend/client/components/Home/Intro.tsx index 37d96bf4..e804acf1 100644 --- a/frontend/client/components/Home/Intro.tsx +++ b/frontend/client/components/Home/Intro.tsx @@ -32,9 +32,10 @@ const HomeIntro: React.SFC = ({ t, authUser }) => (
-
- -
+
); From a2717242660d03fb614b05d039e900987af7c2d9 Mon Sep 17 00:00:00 2001 From: AMStrix Date: Thu, 7 Mar 2019 14:04:40 -0600 Subject: [PATCH 28/72] BE: add SESSION_COOKIE_DOMAIN setting (#311) --- admin/src/components/MFAuth/index.tsx | 2 +- backend/.env.example | 3 +++ backend/grant/settings.py | 5 ++++- 3 files changed, 8 insertions(+), 2 deletions(-) diff --git a/admin/src/components/MFAuth/index.tsx b/admin/src/components/MFAuth/index.tsx index b236a6c4..704ab183 100644 --- a/admin/src/components/MFAuth/index.tsx +++ b/admin/src/components/MFAuth/index.tsx @@ -173,7 +173,7 @@ class MFAuth extends React.Component {
  1. Save two-factor recovery codes
  2. - Setup up TOTP authentication device, typically a smartphone with Google + Setup TOTP authentication device, typically a smartphone with Google Authenticator, Authy, 1Password or other compatible authenticator app.
diff --git a/backend/.env.example b/backend/.env.example index 0c54b4ec..2ab25783 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -7,6 +7,9 @@ REDISTOGO_URL="redis://localhost:6379" SECRET_KEY="not-so-secret" SENDGRID_API_KEY="optional, but emails won't send without it" +# set this so third-party cookie blocking doesn't kill backend sessions (production) +# SESSION_COOKIE_DOMAIN="zfnd.org" + # SENTRY_DSN="https://PUBLICKEY@sentry.io/PROJECTID" # SENTRY_RELEASE="optional, provides sentry logging with release info" diff --git a/backend/grant/settings.py b/backend/grant/settings.py index 1dcf6701..cabb52eb 100644 --- a/backend/grant/settings.py +++ b/backend/grant/settings.py @@ -16,7 +16,7 @@ ENV = env.str("FLASK_ENV", default="production") DEBUG = ENV == "development" SITE_URL = env.str('SITE_URL', default='https://zfnd.org') SQLALCHEMY_DATABASE_URI = env.str("DATABASE_URL") -SQLALCHEMY_ECHO = False # True will print queries to log +SQLALCHEMY_ECHO = False # True will print queries to log QUEUES = ["default"] SECRET_KEY = env.str("SECRET_KEY") BCRYPT_LOG_ROUNDS = env.int("BCRYPT_LOG_ROUNDS", default=13) @@ -25,6 +25,9 @@ DEBUG_TB_INTERCEPT_REDIRECTS = False CACHE_TYPE = "simple" # Can be "memcached", "redis", etc. SQLALCHEMY_TRACK_MODIFICATIONS = False +# so backend session cookies are first-party +SESSION_COOKIE_DOMAIN = env.str('SESSION_COOKIE_DOMAIN', default=None) + SENDGRID_API_KEY = env.str("SENDGRID_API_KEY", default="") SENDGRID_DEFAULT_FROM = "noreply@zfnd.org" From 1905dfe7bfa4ec4402a50784a5ee64a49196fb01 Mon Sep 17 00:00:00 2001 From: William O'Beirne Date: Fri, 8 Mar 2019 16:13:13 -0500 Subject: [PATCH 29/72] Frontend error handlers (#312) * Add componentDidCatch around routes with template, and outside routes at the top level. * Remove error from ErrorWrap on navigation. --- frontend/client/Routes.tsx | 10 ++-- .../client/components/ErrorScreen/index.less | 52 ++++++++++++++++++ .../client/components/ErrorScreen/index.tsx | 53 +++++++++++++++++++ frontend/client/components/ErrorWrap.tsx | 45 ++++++++++++++++ frontend/client/components/Footer/style.less | 5 +- frontend/client/components/Header/style.less | 4 +- frontend/client/index.tsx | 17 +++--- frontend/client/pages/proposal.tsx | 3 -- 8 files changed, 171 insertions(+), 18 deletions(-) create mode 100644 frontend/client/components/ErrorScreen/index.less create mode 100644 frontend/client/components/ErrorScreen/index.tsx create mode 100644 frontend/client/components/ErrorWrap.tsx diff --git a/frontend/client/Routes.tsx b/frontend/client/Routes.tsx index 03425289..c39d2d36 100644 --- a/frontend/client/Routes.tsx +++ b/frontend/client/Routes.tsx @@ -11,6 +11,9 @@ import { import loadable from '@loadable/component'; import AuthRoute from 'components/AuthRoute'; import Template, { TemplateProps } from 'components/Template'; +import ErrorWrap from 'components/ErrorWrap'; +import Loader from 'components/Loader'; +import 'styles/style.less'; // wrap components in loadable...import & they will be split // Make sure you specify chunkname! Must replace slashes with dashes. @@ -38,9 +41,6 @@ const ArbiterEmail = loadable(() => import('pages/email-arbiter'), opts); const RFP = loadable(() => import('pages/rfp'), opts); const RFPs = loadable(() => import('pages/rfps'), opts); -import 'styles/style.less'; -import Loader from 'components/Loader'; - interface RouteConfig extends RouteProps { route: RouteProps; template: TemplateProps; @@ -336,7 +336,9 @@ class Routes extends React.PureComponent { return ( ); } diff --git a/frontend/client/components/ErrorScreen/index.less b/frontend/client/components/ErrorScreen/index.less new file mode 100644 index 00000000..6c5280e2 --- /dev/null +++ b/frontend/client/components/ErrorScreen/index.less @@ -0,0 +1,52 @@ +@import '~styles/variables.less'; + +.ErrorScreen { + padding: 2rem 0; + + &-desc { + font-size: 1.25rem; + line-height: 1.6; + + &-error { + display: block; + font-size: 1rem; + padding: 1rem; + margin-bottom: 1rem; + background: rgba(#000, 0.03); + border: 1px solid rgba(#000, 0.1); + border-radius: 4px; + } + } + + &-buttons { + .ant-btn { + margin-right: 1rem; + } + } + + // Antd overrides + .antd-pro-exception-exception { + min-height: 0; + + .antd-pro-exception-imgBlock { + flex: 0 0 45%; + width: 45%; + + @media @tablet-query { + display: none; + } + } + + .antd-pro-exception-content { + padding-right: 2rem; + + @media @tablet-query { + padding-right: 0; + } + + h1 { + font-size: 4.2rem; + } + } + } +} \ No newline at end of file diff --git a/frontend/client/components/ErrorScreen/index.tsx b/frontend/client/components/ErrorScreen/index.tsx new file mode 100644 index 00000000..dd424407 --- /dev/null +++ b/frontend/client/components/ErrorScreen/index.tsx @@ -0,0 +1,53 @@ +import React from 'react'; +import * as Sentry from '@sentry/browser'; +import { Button } from 'antd'; +import Exception from 'ant-design-pro/lib/Exception'; +import './index.less'; + +interface Props { + error: Error; +} + +export default class ErrorScreen extends React.PureComponent { + componentDidMount() { + const { error } = this.props; + Sentry.captureException(error); + console.error('Error screen showing due to the following error:', error); + } + + render() { + const { error } = this.props; + return ( +
+ +

Something went wrong, and we've logged the following error:

+ {error.message} +

+ Our developers will get right on fixing it. You can either return home and + try again, or open an issue on Github to provide us some more details. +

+
+ } + actions={ + + } + /> +
+ ); + } +} diff --git a/frontend/client/components/ErrorWrap.tsx b/frontend/client/components/ErrorWrap.tsx new file mode 100644 index 00000000..6ae20555 --- /dev/null +++ b/frontend/client/components/ErrorWrap.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import ErrorScreen from './ErrorScreen'; + +interface Props { + children: React.ReactNode; + isFullscreen?: boolean; +} + +interface State { + error: Error | null; +} + +export default class ErrorWrap extends React.Component { + state: State = { + error: null, + }; + + componentDidCatch(error: Error) { + this.setState({ error }); + } + + render() { + let style; + if (this.props.isFullscreen) { + style = { + display: 'flex', + justifyContent: 'center', + alignItems: 'center', + width: '100vw', + minHeight: '100vh', + padding: '0 2rem', + }; + } + + if (this.state.error) { + return ( +
+ +
+ ); + } else { + return this.props.children; + } + } +} diff --git a/frontend/client/components/Footer/style.less b/frontend/client/components/Footer/style.less index bc680427..f312d54e 100644 --- a/frontend/client/components/Footer/style.less +++ b/frontend/client/components/Footer/style.less @@ -51,8 +51,9 @@ } &-logo { - height: 1.8rem; - margin-bottom: 1rem; + height: 6.8rem; + margin-bottom: 0.5rem; + margin-top: -1.25rem; } } diff --git a/frontend/client/components/Header/style.less b/frontend/client/components/Header/style.less index cc053bd1..67dcc3d9 100644 --- a/frontend/client/components/Header/style.less +++ b/frontend/client/components/Header/style.less @@ -1,5 +1,5 @@ @import '~styles/variables.less'; -@header-height: 62px; +@header-height: 68px; @header-transition: 200ms; @link-padding: 0.7rem; @small-query: ~'(max-width: 820px)'; @@ -54,7 +54,7 @@ } &-logo { - height: 1.4rem; + height: 3.6rem; width: auto; transition: transform @header-transition ease; diff --git a/frontend/client/index.tsx b/frontend/client/index.tsx index 7462fa61..dc0f7c59 100644 --- a/frontend/client/index.tsx +++ b/frontend/client/index.tsx @@ -12,6 +12,7 @@ import history from 'store/history'; import { massageSerializedState } from 'utils/api'; import Routes from './Routes'; import i18n from './i18n'; +import ErrorWrap from 'components/ErrorWrap'; Sentry.init({ dsn: process.env.SENTRY_DSN, @@ -26,13 +27,15 @@ i18n.changeLanguage(i18nLanguage.locale); i18n.addResourceBundle(i18nLanguage.locale, 'common', i18nLanguage.resources, true); const App = hot(module)(() => ( - - - - - - - + + + + + + + + + )); loadableReady(() => { diff --git a/frontend/client/pages/proposal.tsx b/frontend/client/pages/proposal.tsx index 93f10476..1e033dc8 100644 --- a/frontend/client/pages/proposal.tsx +++ b/frontend/client/pages/proposal.tsx @@ -7,9 +7,6 @@ import { withRouter, RouteComponentProps } from 'react-router'; type RouteProps = RouteComponentProps; class ProposalPage extends Component { - constructor(props: RouteProps) { - super(props); - } render() { const proposalId = extractIdFromSlug(this.props.match.params.id); return ; From 403fb095132cc9b9d56fbdfdbd1735b7c73d7085 Mon Sep 17 00:00:00 2001 From: Sonya Mann Date: Tue, 12 Mar 2019 09:38:35 -0700 Subject: [PATCH 30/72] change non-attributed contributions language (#316) * non-attributed contributions copy --- .../client/components/ContributionModal/index.tsx | 14 +++++++------- .../components/Proposal/CampaignBlock/index.tsx | 4 ++-- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/frontend/client/components/ContributionModal/index.tsx b/frontend/client/components/ContributionModal/index.tsx index f3448a20..6c769fe0 100644 --- a/frontend/client/components/ContributionModal/index.tsx +++ b/frontend/client/components/ContributionModal/index.tsx @@ -89,19 +89,19 @@ export default class ContributionModal extends React.Component { - You are about to contribute anonymously. Your contribution will show up - without attribution, and even if you're logged in, will not appear anywhere + Your contribution will show up + without attribution. Even if you're logged in, the contribution will not appear anywhere on your account after you close this modal.

- In the case of a refund, your contribution will be treated as a donation to - the Zcash Foundation instead. + ZF Grants is unable to offer refunds for non-attributed contributions. If refunds for this campaign are issued, your contribution will be treated as a donation to + the Zcash Foundation.

- If you would like to have your contribution attached to an account, you can + If you would like to have your contribution attached to an account and remain eligible for refunds, you can close this modal, make sure you're logged in, and don't check the - "Contribute anonymously" checkbox. + "Contribute without attribution" checkbox. } /> diff --git a/frontend/client/components/Proposal/CampaignBlock/index.tsx b/frontend/client/components/Proposal/CampaignBlock/index.tsx index 7b32ba81..0721bcb0 100644 --- a/frontend/client/components/Proposal/CampaignBlock/index.tsx +++ b/frontend/client/components/Proposal/CampaignBlock/index.tsx @@ -191,8 +191,8 @@ export class ProposalCampaignBlock extends React.Component { {amountToRaise && !!authUser && ( - Contribute anonymously - + Contribute without attribution + From e98f266100eb2d7ba9752780e8513f5b1d0e5584 Mon Sep 17 00:00:00 2001 From: Sonya Mann Date: Tue, 12 Mar 2019 09:44:16 -0700 Subject: [PATCH 31/72] add CoC (#328) --- .../client/static/markdown/CODE_OF_CONDUCT.md | 80 ++++++++++++++++--- 1 file changed, 69 insertions(+), 11 deletions(-) diff --git a/frontend/client/static/markdown/CODE_OF_CONDUCT.md b/frontend/client/static/markdown/CODE_OF_CONDUCT.md index 380558f0..7dfaf42e 100644 --- a/frontend/client/static/markdown/CODE_OF_CONDUCT.md +++ b/frontend/client/static/markdown/CODE_OF_CONDUCT.md @@ -1,21 +1,79 @@ -# Code of Conduct +# Community Guidelines -### Lorem Ipsum +Commenters on ZF Grants should strive to be kind, respectful, and interesting. Ideally, your presence in the community should improve the experience for everyone. -Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. + -### Duis Aute Irure +## [Rules](#rules) -Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. +We are committed to making participation in this forum a harassment-free experience for everyone, regardless of a person's... -Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. +* Level of experience. +* Gender identity and expression. +* Sexual orientation. +* Disability. +* Personal appearance. +* Body size. +* Race. +* Ethnicity. +* Age. +* Religion. +* Nationality. -### Ullamco Laboris Reprehenderit +Examples of **unacceptable** behavior include: -Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. +* Using sexualized language or imagery. +* Personal attacks. +* Trolling and insulting or derogatory comments. +* Public or private harassment. +* Publishing another person's private information, such as physical or electronic addresses, without explicit permission (AKA doxxing). +* Other unethical or unprofessional conduct, at the discretion of the ZF Grants admins. -### Fugiat + -Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. +## [Moderation](#moderation) -**Effective:** Oct 25, 2018 +ZF Grants has the right and responsibility to remove, edit, or reject posts, comments and other contributions that are not aligned with these Community Guidelines, or to suspend or ban any ZF Grants user for other behaviors that they deem inappropriate, threatening, offensive, or harmful. + +By adopting these Community Guidelines, the admins of ZF Grants commit themselves to fairly and consistently applying these principles to every aspect of managing discussions on the platform. + + + +## [Reporting](#reporting) + +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by flagging the post or comment in question, or by emailing contact@zfnd.org. + +All complaints will be reviewed, investigated, and will result in a response that is deemed necessary and appropriate for the circumstances. **ZF Grants is obligated to maintain confidentiality with regard to the reporter of an incident.** + +_The guidelines listed in the sections above were adapted from the [rules of the Zcash Community Forum](https://forum.zcashcommunity.com/faq), which came from the [Zcash core contributor code of conduct](https://github.com/zcash/zcash/blob/master/code_of_conduct.md), which is itself based on [version 1.3.0 of the Contributor Covenant](https://www.contributor-covenant.org/version/1/3/0/code-of-conduct.html)._ + + + +## [Etiquette](#etiquette) + +Please do not: + +* Post topics unrelated to Zcash. +* Solicit domain sales (even if related to Zcash). +* Bump comments sections without something new to add. +* Derail topics. +* Post links to phishing sites. +* Post spam. +* Create topics or replies for the sole purpose of causing unrest. +* Repost or comment excessively. +* Create and use multiple accounts as sockpuppets. +* Post solicitations for or about services considered illegal in the United States. +* Post links to internet gambling operations. + * It's a crime for internet gambling operations to accept bets from United States persons, and the Justice Department takes the position that people who post advertisements or promotions for online gambling may be aiding and abetting those activities. + + + +## [Copyright](#stealing) + +You may not post anything digital that belongs to someone else without permission. You may not post descriptions of, links to, or methods for stealing someone’s intellectual property (software, video, audio, images), or for breaking any other law. + + + +## [Legal](#tos) + +To use ZF Grants, you must agree to abide by our [Terms of Service](/tos). The data that constitutes your ZF Grants account and its activities is covered by our [Privacy Policy](/privacy). From c291d41d4e696a147aef205e03277675f15df9b1 Mon Sep 17 00:00:00 2001 From: AMStrix Date: Tue, 12 Mar 2019 12:08:16 -0500 Subject: [PATCH 32/72] Admin financials (#310) * admin: financials basics * BE: allow missing emailSettings on user settings EP * admin: add webpack import loader for ant-design-pro (for styles loading) * BE: admin financials reporting * admin: financials page * admin financials - convert contribution and payout total summations to sql * Handle some kinda failure case. * admin: remove "accounting" card from financials --- admin/src/Routes.tsx | 2 + admin/src/components/Financials/index.less | 56 +++++++++ admin/src/components/Financials/index.tsx | 138 +++++++++++++++++++++ admin/src/components/Template/index.tsx | 6 + admin/src/store.ts | 41 ++++++ admin/webpack.config.js | 10 ++ backend/grant/admin/views.py | 98 ++++++++++++++- backend/grant/user/views.py | 2 +- 8 files changed, 351 insertions(+), 2 deletions(-) create mode 100644 admin/src/components/Financials/index.less create mode 100644 admin/src/components/Financials/index.tsx diff --git a/admin/src/Routes.tsx b/admin/src/Routes.tsx index 5026b646..24a2cbcd 100644 --- a/admin/src/Routes.tsx +++ b/admin/src/Routes.tsx @@ -19,6 +19,7 @@ import RFPDetail from 'components/RFPDetail'; import Contributions from 'components/Contributions'; import ContributionForm from 'components/ContributionForm'; import ContributionDetail from 'components/ContributionDetail'; +import Financials from 'components/Financials'; import Moderation from 'components/Moderation'; import Settings from 'components/Settings'; @@ -54,6 +55,7 @@ class Routes extends React.Component { + } /> diff --git a/admin/src/components/Financials/index.less b/admin/src/components/Financials/index.less new file mode 100644 index 00000000..a72948cd --- /dev/null +++ b/admin/src/components/Financials/index.less @@ -0,0 +1,56 @@ +.Financials { + &-zcash { + font-size: 0.8rem; + vertical-align: text-top; + display: inline-block; + padding-top: 0.15rem; + } + + &-bottomLine { + & > div { + display: flex; + justify-content: space-between; + + &.is-net { + margin-top: 0.5rem; + border-top: 1px solid rgba(0, 0, 0, 0.65); + padding-top: 0.5rem; + } + + & .Info { + font-size: 0.9rem; + } + + & > div + div { + font-family: 'Courier New', Courier, monospace; + min-width: 250px; + } + + small { + display: inline-block; + padding-right: 0.1rem; + } + } + font-size: 1.2rem; + } + + h1 { + font-size: 1.5rem; + } + + .ant-card { + margin-bottom: 16px; + } + + .antd-pro-charts-pie-total { + .pie-sub-title { + margin: 0; + } + } + + .antd-pro-charts-pie-legend { + li { + margin-bottom: 0; + } + } +} diff --git a/admin/src/components/Financials/index.tsx b/admin/src/components/Financials/index.tsx new file mode 100644 index 00000000..e085475e --- /dev/null +++ b/admin/src/components/Financials/index.tsx @@ -0,0 +1,138 @@ +import React from 'react'; +import { Spin, Card, Row, Col } from 'antd'; +import { Charts } from 'ant-design-pro'; +import { view } from 'react-easy-state'; +import store from '../../store'; +import Info from 'components/Info'; +import './index.less'; + +class Financials extends React.Component { + componentDidMount() { + store.fetchFinancials(); + } + + render() { + const { contributions, grants, payouts } = store.financials; + if (!store.financialsFetched) { + return ; + } + + return ( +
+ + + + ( + + )} + data={[ + { x: 'funded', y: parseFloat(contributions.funded) }, + { x: 'funding', y: parseFloat(contributions.funding) }, + { x: 'refunding', y: parseFloat(contributions.refunding) }, + { x: 'refunded', y: parseFloat(contributions.refunded) }, + { x: 'staking', y: parseFloat(contributions.staking) }, + ]} + valueFormat={val => } + height={180} + /> + + + +

+ Matching and bounty obligations for active and completed + proposals. +

+ matching - total matching amount pleged +
+ bounties - total bounty amount pledged +
+ + } + > + Grants + + } + > + ( + + )} + data={[ + { x: 'bounties', y: parseFloat(grants.bounty) }, + { x: 'matching', y: parseFloat(grants.matching) }, + ]} + valueFormat={val => } + height={180} + /> +
+ + +

Milestone payouts.

+ due - payouts currently accepted but not paid +
+ future - payouts that are not yet paid, but expected to be + requested in the future +
+ paid - total milestone payouts marked as paid, regardless of + proposal status +
+ + } + > + Payouts + + } + > + ( + + )} + data={[ + { x: 'due', y: parseFloat(payouts.due) }, + { x: 'future', y: parseFloat(payouts.future) }, + { x: 'paid', y: parseFloat(payouts.paid) }, + ]} + valueFormat={val => } + height={180} + /> +
+ +
+
+ ); + } +} + +export default view(Financials); diff --git a/admin/src/components/Template/index.tsx b/admin/src/components/Template/index.tsx index 70f840ce..4954f3a2 100644 --- a/admin/src/components/Template/index.tsx +++ b/admin/src/components/Template/index.tsx @@ -63,6 +63,12 @@ class Template extends React.Component { Contributions + + + + Financials + + diff --git a/admin/src/store.ts b/admin/src/store.ts index b562f7f9..e8d6da98 100644 --- a/admin/src/store.ts +++ b/admin/src/store.ts @@ -74,6 +74,11 @@ async function fetchStats() { return data; } +async function fetchFinancials() { + const { data } = await api.get('/admin/financials'); + return data; +} + async function fetchUsers(params: Partial) { const { data } = await api.get('/admin/users', { params }); return data; @@ -219,6 +224,31 @@ const app = store({ contributionRefundableCount: 0, }, + financialsFetched: false, + financialsFetching: false, + financials: { + grants: { + total: '0', + matching: '0', + bounty: '0', + }, + contributions: { + total: '0', + gross: '0', + staking: '0', + funding: '0', + funded: '0', + refunding: '0', + refunded: '0', + }, + payouts: { + total: '0', + due: '0', + paid: '0', + future: '0', + }, + }, + users: { page: createDefaultPageData('EMAIL:DESC'), }, @@ -346,6 +376,17 @@ const app = store({ app.statsFetching = false; }, + async fetchFinancials() { + app.financialsFetching = true; + try { + app.financials = await fetchFinancials(); + app.financialsFetched = true; + } catch (e) { + handleApiError(e); + } + app.financialsFetching = false; + }, + // Users async fetchUsers() { diff --git a/admin/webpack.config.js b/admin/webpack.config.js index 27f3d354..4e0cfc30 100644 --- a/admin/webpack.config.js +++ b/admin/webpack.config.js @@ -41,6 +41,16 @@ module.exports = { '@babel/plugin-proposal-object-rest-spread', '@babel/plugin-proposal-class-properties', ['import', { libraryName: 'antd', style: true }], + [ + 'import', + { + libraryName: 'ant-design-pro', + libraryDirectory: 'lib', + style: true, + camel2DashComponentName: false, + }, + 'antdproimport', + ], ], presets: ['@babel/react', ['@babel/env', { useBuiltIns: 'entry' }]], }, diff --git a/backend/grant/admin/views.py b/backend/grant/admin/views.py index 91ae8aa9..7443b1a5 100644 --- a/backend/grant/admin/views.py +++ b/backend/grant/admin/views.py @@ -4,7 +4,7 @@ from functools import reduce from flask import Blueprint, request from marshmallow import fields, validate -from sqlalchemy import func, or_ +from sqlalchemy import func, or_, text import grant.utils.admin as admin import grant.utils.auth as auth @@ -712,3 +712,99 @@ def edit_comment(comment_id, hidden, reported): db.session.commit() return admin_comment_schema.dump(comment) + + +# Financials + +@blueprint.route("/financials", methods=["GET"]) +@admin.admin_auth_required +def financials(): + + nfmt = '999999.99999999' # smallest unit of ZEC + + def sql_pc(where: str): + return f"SELECT SUM(TO_NUMBER(amount, '{nfmt}')) FROM proposal_contribution WHERE {where}" + + def sql_pc_p(where: str): + return f''' + SELECT SUM(TO_NUMBER(amount, '{nfmt}')) + FROM proposal_contribution as pc + INNER JOIN proposal as p ON pc.proposal_id = p.id + WHERE {where} + ''' + + def sql_ms(where: str): + return f''' + SELECT SUM(TO_NUMBER(ms.payout_percent, '999')/100 * TO_NUMBER(p.target, '999999.99999999')) + FROM milestone as ms + INNER JOIN proposal as p ON ms.proposal_id = p.id + WHERE {where} + ''' + + def ex(sql: str): + res = db.engine.execute(text(sql)) + return [row[0] if row[0] else Decimal(0) for row in res][0].normalize() + + contributions = { + 'total': str(ex(sql_pc("status = 'CONFIRMED' AND staking = FALSE"))), + 'staking': str(ex(sql_pc("status = 'CONFIRMED' AND staking = TRUE"))), + 'funding': str(ex(sql_pc_p("pc.status = 'CONFIRMED' AND pc.staking = FALSE AND p.stage = 'FUNDING_REQUIRED'"))), + 'funded': str(ex(sql_pc_p("pc.status = 'CONFIRMED' AND pc.staking = FALSE AND p.stage in ('WIP', 'COMPLETED')"))), + 'refunding': str(ex(sql_pc_p( + "pc.status = 'CONFIRMED' AND pc.staking = FALSE AND pc.refund_tx_id IS NULL AND p.stage IN ('CANCELED', 'FAILED')" + ))), + 'refunded': str(ex(sql_pc_p( + "pc.status = 'CONFIRMED' AND pc.staking = FALSE AND pc.refund_tx_id IS NOT NULL AND p.stage IN ('CANCELED', 'FAILED')" + ))), + 'gross': str(ex(sql_pc_p("pc.status = 'CONFIRMED' AND pc.refund_tx_id IS NULL"))), + } + + po_due = ex(sql_ms("ms.stage = 'ACCEPTED'")) # payments accepted but not yet marked as paid + po_paid = ex(sql_ms("ms.stage = 'PAID'")) # will catch paid ms from all proposals regardless of status/stage + # expected payments + po_future = ex(sql_ms("ms.stage IN ('IDLE', 'REJECTED', 'REQUESTED') AND p.stage IN ('WIP', 'COMPLETED')")) + po_total = po_due + po_paid + po_future + + payouts = { + 'total': str(po_total), + 'due': str(po_due), + 'paid': str(po_paid), + 'future': str(po_future), + } + + grants = { + 'total': '0', + 'matching': '0', + 'bounty': '0', + } + + def add_str_dec(a: str, b: str): + return str(Decimal(a) + Decimal(b)) + + proposals = Proposal.query.all() + + for p in proposals: + # CANCELED proposals excluded, though they could have had milestones paid out with grant funds + if p.stage in [ProposalStage.WIP, ProposalStage.COMPLETED]: + # matching + matching = Decimal(p.contributed) * Decimal(p.contribution_matching) + remaining = Decimal(p.target) - Decimal(p.contributed) + if matching > remaining: + matching = remaining + + # bounty + bounty = Decimal(p.contribution_bounty) + remaining = Decimal(p.target) - (matching + Decimal(p.contributed)) + if bounty > remaining: + bounty = remaining + + grants['matching'] = add_str_dec(grants['matching'], matching) + grants['bounty'] = add_str_dec(grants['bounty'], bounty) + grants['total'] = add_str_dec(grants['total'], matching + bounty) + + return { + 'grants': grants, + 'contributions': contributions, + 'payouts': payouts, + 'net': str(Decimal(contributions['gross']) - Decimal(payouts['paid'])) + } diff --git a/backend/grant/user/views.py b/backend/grant/user/views.py index 4dc0407d..9d510471 100644 --- a/backend/grant/user/views.py +++ b/backend/grant/user/views.py @@ -342,7 +342,7 @@ def get_user_settings(user_id): @auth.requires_same_user_auth # TODO guard all (shape, validity) @body({ - "emailSubscriptions": fields.Dict(required=True), + "emailSubscriptions": fields.Dict(required=False, missing=None), "refundAddress": fields.Str(required=False, missing=None) }) def set_user_settings(user_id, email_subscriptions, refund_address): From c6ad668d712f59a815034ee464e2938030c6fd31 Mon Sep 17 00:00:00 2001 From: William O'Beirne Date: Tue, 12 Mar 2019 13:10:56 -0400 Subject: [PATCH 33/72] Better contribution error displays (#314) * Handle blockchain_get failures better. * tsc --- backend/grant/app.py | 4 +++ backend/grant/proposal/models.py | 5 +++- .../components/ContributionModal/index.tsx | 14 ++++++++-- .../client/components/CreateFlow/Final.tsx | 26 ++++++++++++++++--- .../components/Profile/ProfilePending.tsx | 3 ++- 5 files changed, 45 insertions(+), 7 deletions(-) diff --git a/backend/grant/app.py b/backend/grant/app.py index 6c1953ec..d347aae2 100644 --- a/backend/grant/app.py +++ b/backend/grant/app.py @@ -48,6 +48,10 @@ def create_app(config_objects=["grant.settings"]): return jsonify({"message": error_message}), err.code, headers else: return jsonify({"message": error_message}), err.code + + @app.errorhandler(Exception) + def handle_exception(err): + return jsonify({"message": "Something went wrong"}), 500 for conf in config_objects: app.config.from_object(conf) diff --git a/backend/grant/proposal/models.py b/backend/grant/proposal/models.py index de2d984c..22a44834 100644 --- a/backend/grant/proposal/models.py +++ b/backend/grant/proposal/models.py @@ -286,7 +286,10 @@ class Proposal(db.Model): raise ValidationException("Proposal must have a {}".format(field)) # Check with node that the address is kosher - res = blockchain_get('/validate/address', {'address': self.payout_address}) + try: + res = blockchain_get('/validate/address', {'address': self.payout_address}) + except: + raise ValidationException("Could not validate your payout address due to an internal server error, please try again later") if not res['valid']: raise ValidationException("Payout address is not a valid Zcash address") diff --git a/frontend/client/components/ContributionModal/index.tsx b/frontend/client/components/ContributionModal/index.tsx index 6c769fe0..9a8a903f 100644 --- a/frontend/client/components/ContributionModal/index.tsx +++ b/frontend/client/components/ContributionModal/index.tsx @@ -66,11 +66,12 @@ export default class ContributionModal extends React.Component { if (contribution !== this.props.contribution) { this.setState({ contribution: contribution || null }); } - // When the modal is closed, clear out the contribution and anonymous check + // When the modal is closed, clear out the contribution, error, and anonymous check if (this.props.isVisible && !isVisible) { this.setState({ contribution: null, hasConfirmedAnonymous: false, + error: null, }); } } @@ -133,7 +134,16 @@ export default class ContributionModal extends React.Component { if (error) { okText = 'Done'; onOk = handleClose; - content = error; + content = ( + + ); } else { okText = 'I’ve sent it'; onOk = this.confirmSend; diff --git a/frontend/client/components/CreateFlow/Final.tsx b/frontend/client/components/CreateFlow/Final.tsx index e5adadc4..81d5eaa0 100644 --- a/frontend/client/components/CreateFlow/Final.tsx +++ b/frontend/client/components/CreateFlow/Final.tsx @@ -2,6 +2,7 @@ import React from 'react'; import { connect } from 'react-redux'; import { Icon } from 'antd'; import { Link } from 'react-router-dom'; +import Result from 'ant-design-pro/lib/Result'; import Loader from 'components/Loader'; import { createActions } from 'modules/create'; import { AppState } from 'store/reducers'; @@ -28,6 +29,7 @@ type Props = OwnProps & StateProps & DispatchProps; const STATE = { contribution: null as null | ContributionWithAddresses, + contributionError: null as null | Error, }; type State = typeof STATE; @@ -49,7 +51,7 @@ class CreateFinal extends React.Component { render() { const { submittedProposal, submitError, goBack } = this.props; - const { contribution } = this.state; + const { contribution, contributionError } = this.state; const ready = submittedProposal && (submittedProposal.isStaked || contribution); const staked = submittedProposal && submittedProposal.isStaked; @@ -120,6 +122,20 @@ class CreateFinal extends React.Component { )} ); + } else if (contributionError) { + content = ( + + We were unable to get your staking contribution started. You can finish + staking from your profile, please try + again from there soon. + + } + /> + ); } else { content = ; } @@ -136,8 +152,12 @@ class CreateFinal extends React.Component { private getStakingContribution = async () => { const { submittedProposal } = this.props; if (submittedProposal) { - const res = await getProposalStakingContribution(submittedProposal.proposalId); - this.setState({ contribution: res.data }); + try { + const res = await getProposalStakingContribution(submittedProposal.proposalId); + this.setState({ contribution: res.data }); + } catch (err) { + this.setState({ contributionError: err }); + } } }; } diff --git a/frontend/client/components/Profile/ProfilePending.tsx b/frontend/client/components/Profile/ProfilePending.tsx index ba0c120f..4332ebfa 100644 --- a/frontend/client/components/Profile/ProfilePending.tsx +++ b/frontend/client/components/Profile/ProfilePending.tsx @@ -194,7 +194,8 @@ class ProfilePending extends React.Component { this.setState({ isLoadingStake: false }); }); } catch (err) { - message.error(err.message, 3); + console.error(err); + message.error('Failed to get staking contribution, try again later', 3); this.setState({ isLoadingStake: false }); } }; From 671e65e145e6ff42ac58fbd2b063632c52692d72 Mon Sep 17 00:00:00 2001 From: William O'Beirne Date: Tue, 12 Mar 2019 15:14:30 -0400 Subject: [PATCH 34/72] Upgrade react-mde to 7.0.4 (#331) --- frontend/package.json | 2 +- frontend/yarn.lock | 1108 ++--------------------------------------- 2 files changed, 40 insertions(+), 1070 deletions(-) diff --git a/frontend/package.json b/frontend/package.json index 512f49f1..90231faf 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -129,7 +129,7 @@ "react-helmet": "^5.2.0", "react-hot-loader": "^4.3.8", "react-i18next": "^8.3.5", - "react-mde": "7.0.3", + "react-mde": "7.0.4", "react-redux": "^5.0.7", "react-router": "4.4.0-beta.6", "react-router-dom": "4.4.0-beta.6", diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 1aef15a7..5c2f5ad3 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -2178,18 +2178,11 @@ version "4.2.1" resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.1.tgz#5c85d662f76fa1d34575766c5dcd6615abcd30d8" -JSONStream@^1.3.3: - version "1.3.4" - resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.4.tgz#615bb2adb0cd34c8f4c447b5f6512fa1d8f16a2e" - dependencies: - jsonparse "^1.2.0" - through ">=2.2.7 <3" - abab@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.0.tgz#aba0ab4c5eee2d4c79d3487d85450fb2376ebb0f" -abbrev@1, abbrev@~1.1.1: +abbrev@1: version "1.1.1" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" @@ -2250,24 +2243,6 @@ address@1.0.3, address@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/address/-/address-1.0.3.tgz#b5f50631f8d6cec8bd20c963963afb55e06cbce9" -agent-base@4, agent-base@~4.2.0: - version "4.2.1" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9" - dependencies: - es6-promisify "^5.0.0" - -agent-base@^4.1.0: - version "4.1.2" - resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.1.2.tgz#80fa6cde440f4dcf9af2617cf246099b5d99f0c8" - dependencies: - es6-promisify "^5.0.0" - -agentkeepalive@^3.4.1: - version "3.5.1" - resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-3.5.1.tgz#4eba75cf2ad258fc09efd506cdb8d8c2971d35a4" - dependencies: - humanize-ms "^1.2.1" - "airbnb-js-shims@^1 || ^2": version "2.1.1" resolved "https://registry.yarnpkg.com/airbnb-js-shims/-/airbnb-js-shims-2.1.1.tgz#a509611480db7e6d9db62fe2acfaeb473b6842ac" @@ -2375,14 +2350,6 @@ ansi-styles@^3.2.0, ansi-styles@^3.2.1: dependencies: color-convert "^1.9.0" -ansicolors@~0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/ansicolors/-/ansicolors-0.3.2.tgz#665597de86a9ffe3aa9bfbe6cae5c6ea426b4979" - -ansistyles@~0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/ansistyles/-/ansistyles-0.1.3.tgz#5de60415bda071bb37127854c864f41b23254539" - ant-design-palettes@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/ant-design-palettes/-/ant-design-palettes-1.1.3.tgz#84119b1a4d86363adc52a38d587e65336a0a27dd" @@ -2494,18 +2461,10 @@ app-root-dir@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/app-root-dir/-/app-root-dir-1.0.2.tgz#38187ec2dea7577fff033ffcb12172692ff6e118" -aproba@^1.0.3, aproba@^1.1.1, aproba@^1.1.2, aproba@~1.2.0: +aproba@^1.0.3, aproba@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" -"aproba@^1.1.2 || 2": - version "2.0.0" - resolved "https://registry.yarnpkg.com/aproba/-/aproba-2.0.0.tgz#52520b8ae5b569215b354efc0caa3fe1e45a8adc" - -archy@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/archy/-/archy-1.0.0.tgz#f9c8c13757cc1dd7bc379ac77b2c62a5c2868c40" - are-we-there-yet@~1.1.2: version "1.1.5" resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" @@ -2614,7 +2573,7 @@ arrify@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" -asap@^2.0.0, asap@~2.0.3: +asap@~2.0.3: version "2.0.6" resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.6.tgz#e50347611d7e690943208bbdafebcbc2fb866d46" @@ -3068,16 +3027,6 @@ bignumber.js@^2.1.0: version "2.4.0" resolved "https://registry.yarnpkg.com/bignumber.js/-/bignumber.js-2.4.0.tgz#838a992da9f9d737e0f4b2db0be62bb09dd0c5e8" -bin-links@^1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/bin-links/-/bin-links-1.1.2.tgz#fb74bd54bae6b7befc6c6221f25322ac830d9757" - dependencies: - bluebird "^3.5.0" - cmd-shim "^2.0.2" - gentle-fs "^2.0.0" - graceful-fs "^4.1.11" - write-file-atomic "^2.3.0" - binary-extensions@^1.0.0: version "1.12.0" resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.12.0.tgz#c2d780f53d45bba8317a8902d4ceeaf3a6385b14" @@ -3106,7 +3055,7 @@ block-stream@*: dependencies: inherits "~2.0.0" -bluebird@^3.5.0, bluebird@^3.5.1, bluebird@~3.5.1: +bluebird@^3.5.1: version "3.5.1" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9" @@ -3171,18 +3120,6 @@ boolbase@^1.0.0, boolbase@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" -boom@4.x.x: - version "4.3.1" - resolved "https://registry.yarnpkg.com/boom/-/boom-4.3.1.tgz#4f8a3005cb4a7e3889f749030fd25b96e01d2e31" - dependencies: - hoek "4.x.x" - -boom@5.x.x: - version "5.2.0" - resolved "https://registry.yarnpkg.com/boom/-/boom-5.2.0.tgz#5dd9da6ee3a5f302077436290cb717d3f4a54e02" - dependencies: - hoek "4.x.x" - bowser@^1.7.3: version "1.9.4" resolved "https://registry.yarnpkg.com/bowser/-/bowser-1.9.4.tgz#890c58a2813a9d3243704334fa81b96a5c150c9a" @@ -3358,18 +3295,6 @@ builtin-status-codes@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" -builtins@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/builtins/-/builtins-1.0.3.tgz#cb94faeb61c8696451db36534e1422f94f0aee88" - -byline@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/byline/-/byline-5.0.0.tgz#741c5216468eadc457b03410118ad77de8c1ddb1" - -byte-size@^4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/byte-size/-/byte-size-4.0.3.tgz#b7c095efc68eadf82985fccd9a2df43a74fa2ccd" - bytes@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" @@ -3392,25 +3317,6 @@ cacache@^10.0.4: unique-filename "^1.1.0" y18n "^4.0.0" -cacache@^11.0.1, cacache@^11.0.2, cacache@^11.1.0: - version "11.2.0" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-11.2.0.tgz#617bdc0b02844af56310e411c0878941d5739965" - dependencies: - bluebird "^3.5.1" - chownr "^1.0.1" - figgy-pudding "^3.1.0" - glob "^7.1.2" - graceful-fs "^4.1.11" - lru-cache "^4.1.3" - mississippi "^3.0.0" - mkdirp "^0.5.1" - move-concurrently "^1.0.1" - promise-inflight "^1.0.1" - rimraf "^2.6.2" - ssri "^6.0.0" - unique-filename "^1.1.0" - y18n "^4.0.0" - cache-base@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" @@ -3434,10 +3340,6 @@ cache-loader@^1.2.2: neo-async "^2.5.0" schema-utils "^0.4.2" -call-limit@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/call-limit/-/call-limit-1.1.0.tgz#6fd61b03f3da42a2cd0ec2b60f02bd0e71991fea" - call-me-maybe@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b" @@ -3584,10 +3486,6 @@ chownr@^1.0.1, chownr@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.1.tgz#54726b8b8fff4df053c42187e801fb4412df1494" -chownr@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.0.1.tgz#e2a75042a9551908bebd25b8523d5f9769d79181" - chrome-trace-event@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.0.tgz#45a91bd2c20c9411f0963b5aaeb9a1b95e09cc48" @@ -3598,12 +3496,6 @@ ci-info@^1.3.0, ci-info@^1.5.0: version "1.6.0" resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.6.0.tgz#2ca20dbb9ceb32d4524a683303313f0304b1e497" -cidr-regex@^2.0.8: - version "2.0.9" - resolved "https://registry.yarnpkg.com/cidr-regex/-/cidr-regex-2.0.9.tgz#9c17bb2b18e15af07f7d0c3b994b961d687ed1c9" - dependencies: - ip-regex "^2.1.0" - cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" @@ -3634,13 +3526,6 @@ cli-boxes@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-1.0.0.tgz#4fa917c3e59c94a004cd61f8ee509da651687143" -cli-columns@^3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/cli-columns/-/cli-columns-3.1.2.tgz#6732d972979efc2ae444a1f08e08fa139c96a18e" - dependencies: - string-width "^2.0.0" - strip-ansi "^3.0.1" - cli-cursor@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-1.0.2.tgz#64da3f7d56a54412e59794bd62dc35295e8f2987" @@ -3657,15 +3542,6 @@ cli-spinners@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-0.1.2.tgz#bb764d88e185fb9e1e6a2a1f19772318f605e31c" -cli-table3@^0.5.0: - version "0.5.1" - resolved "https://registry.yarnpkg.com/cli-table3/-/cli-table3-0.5.1.tgz#0252372d94dfc40dbd8df06005f48f31f656f202" - dependencies: - object-assign "^4.1.0" - string-width "^2.1.1" - optionalDependencies: - colors "^1.1.2" - cli-truncate@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-0.2.1.tgz#9f15cfbb0705005369216c626ac7d05ab90dd574" @@ -3719,10 +3595,6 @@ clone-stats@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/clone-stats/-/clone-stats-1.0.0.tgz#b3782dff8bb5474e18b9b6bf0fdfe782f8777680" -clone@^1.0.2: - version "1.0.4" - resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e" - clone@^2.1.1, clone@^2.1.2: version "2.1.2" resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f" @@ -3735,13 +3607,6 @@ cloneable-readable@^1.0.0: process-nextick-args "^2.0.0" readable-stream "^2.3.5" -cmd-shim@^2.0.2, cmd-shim@~2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/cmd-shim/-/cmd-shim-2.0.2.tgz#6fcbda99483a8fd15d7d30a196ca69d688a2efdb" - dependencies: - graceful-fs "^4.1.2" - mkdirp "~0.5.0" - co@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" @@ -3791,10 +3656,6 @@ colornames@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/colornames/-/colornames-1.1.1.tgz#f8889030685c7c4ff9e2a559f5077eb76a816f96" -colors@^1.1.2: - version "1.3.1" - resolved "https://registry.yarnpkg.com/colors/-/colors-1.3.1.tgz#4accdb89cf2cabc7f982771925e9468784f32f3d" - colors@^1.2.1, colors@^1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/colors/-/colors-1.3.2.tgz#2df8ff573dfbf255af562f8ce7181d6b971a359b" @@ -3810,13 +3671,6 @@ colorspace@1.1.x: color "3.0.x" text-hex "1.0.x" -columnify@~1.5.4: - version "1.5.4" - resolved "https://registry.yarnpkg.com/columnify/-/columnify-1.5.4.tgz#4737ddf1c7b69a8a7c340570782e947eec8e78bb" - dependencies: - strip-ansi "^3.0.0" - wcwidth "^1.0.0" - combined-stream@1.0.6, combined-stream@~1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.6.tgz#723e7df6e801ac5613113a7e445a9b69cb632818" @@ -3898,21 +3752,6 @@ concat-stream@1.6.2, concat-stream@^1.5.0: readable-stream "^2.2.2" typedarray "^0.0.6" -concat-stream@^1.5.2: - version "1.6.0" - resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7" - dependencies: - inherits "^2.0.3" - readable-stream "^2.2.2" - typedarray "^0.0.6" - -config-chain@~1.1.11: - version "1.1.11" - resolved "https://registry.yarnpkg.com/config-chain/-/config-chain-1.1.11.tgz#aba09747dfbe4c3e70e766a6e41586e1859fc6f2" - dependencies: - ini "^1.3.4" - proto-list "~1.2.1" - configstore@^3.0.0: version "3.1.2" resolved "https://registry.yarnpkg.com/configstore/-/configstore-3.1.2.tgz#c6f25defaeef26df12dd33414b001fe81a543f8f" @@ -3941,7 +3780,7 @@ console-browserify@^1.1.0: dependencies: date-now "^0.1.4" -console-control-strings@^1.0.0, console-control-strings@^1.1.0, console-control-strings@~1.1.0: +console-control-strings@^1.0.0, console-control-strings@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" @@ -4153,12 +3992,6 @@ crypt@~0.0.1: version "0.0.2" resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" -cryptiles@3.x.x: - version "3.1.2" - resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-3.1.2.tgz#a89fbb220f5ce25ec56e8c4aa8a4fd7b5b0d29fe" - dependencies: - boom "5.x.x" - crypto-browserify@^3.11.0: version "3.12.0" resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" @@ -4466,12 +4299,6 @@ debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.0, debug@^2.6. dependencies: ms "2.0.0" -debug@3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" - dependencies: - ms "2.0.0" - debug@^3.1.0, debug@^3.2.5: version "3.2.6" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" @@ -4485,10 +4312,6 @@ debug@^4.1.0, debug@^4.1.1: dependencies: ms "^2.1.1" -debuglog@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" - decamelize@^1.0.0, decamelize@^1.1.1, decamelize@^1.1.2: version "1.2.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" @@ -4531,12 +4354,6 @@ default-gateway@^4.0.1: execa "^1.0.0" ip-regex "^2.1.0" -defaults@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/defaults/-/defaults-1.0.3.tgz#c656051e9817d9ff08ed881477f3fe4019f3ef7d" - dependencies: - clone "^1.0.2" - define-properties@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.2.tgz#83a73f2fea569898fb737193c8f873caf6d45c94" @@ -4611,18 +4428,10 @@ detect-indent@^4.0.0: dependencies: repeating "^2.0.0" -detect-indent@~5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-5.0.0.tgz#3871cc0a6a002e8c3e5b3cf7f336264675f06b9d" - detect-libc@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" -detect-newline@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2" - detect-node@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/detect-node/-/detect-node-2.0.4.tgz#014ee8f8f669c5c58023da64b8179c083a28c46c" @@ -4642,13 +4451,6 @@ detect-port@^1.2.3: address "^1.0.1" debug "^2.6.0" -dezalgo@^1.0.0, dezalgo@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.3.tgz#7f742de066fc748bc8db820569dddce49bf0d456" - dependencies: - asap "^2.0.0" - wrappy "1" - diagnostics@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/diagnostics/-/diagnostics-1.1.1.tgz#cab6ac33df70c9d9a727490ae43ac995a769b22a" @@ -4832,10 +4634,6 @@ ecc-jsbn@~0.1.1: dependencies: jsbn "~0.1.0" -editor@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/editor/-/editor-1.0.0.tgz#60c7f87bd62bcc6a894fa8ccd6afb7823a24f742" - ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" @@ -4932,10 +4730,6 @@ env-variable@0.0.x: version "0.0.4" resolved "https://registry.yarnpkg.com/env-variable/-/env-variable-0.0.4.tgz#0d6280cf507d84242befe35a512b5ae4be77c54e" -err-code@^1.0.0: - version "1.1.2" - resolved "https://registry.yarnpkg.com/err-code/-/err-code-1.1.2.tgz#06e0116d3028f6aef4806849eb0ea6a748ae6960" - errno@^0.1.1, errno@~0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618" @@ -4990,12 +4784,6 @@ es6-promise@^4.0.3: version "4.1.1" resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.1.1.tgz#8811e90915d9a0dba36274f0b242dbda78f9c92a" -es6-promisify@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" - dependencies: - es6-promise "^4.0.3" - es6-shim@^0.35.3: version "0.35.3" resolved "https://registry.yarnpkg.com/es6-shim/-/es6-shim-0.35.3.tgz#9bfb7363feffff87a6cdb6cd93e405ec3c4b6f26" @@ -5418,10 +5206,6 @@ fecha@^2.3.3, fecha@~2.3.3: version "2.3.3" resolved "https://registry.yarnpkg.com/fecha/-/fecha-2.3.3.tgz#948e74157df1a32fd1b12c3a3c3cdcb6ec9d96cd" -figgy-pudding@^3.0.0, figgy-pudding@^3.1.0, figgy-pudding@^3.2.0: - version "3.4.1" - resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.4.1.tgz#af66da1991fa2f94ff7f33b545a38ea4b3869696" - figures@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e" @@ -5533,10 +5317,6 @@ find-file-up@^1.0.2: dependencies: resolve-dir "^1.0.0" -find-npm-prefix@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/find-npm-prefix/-/find-npm-prefix-1.0.2.tgz#8d8ce2c78b3b4b9e66c8acc6a37c231eb841cfdf" - find-parent-dir@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/find-parent-dir/-/find-parent-dir-0.3.0.tgz#33c44b429ab2b2f0646299c5f9f718f376ff8d54" @@ -5676,13 +5456,6 @@ fresh@0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" -from2@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/from2/-/from2-1.3.0.tgz#88413baaa5f9a597cfde9221d86986cd3c061dfd" - dependencies: - inherits "~2.0.1" - readable-stream "~1.1.10" - from2@^2.1.0: version "2.3.0" resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af" @@ -5720,15 +5493,7 @@ fs-minipass@^1.2.5: dependencies: minipass "^2.2.1" -fs-vacuum@^1.2.10, fs-vacuum@~1.2.10: - version "1.2.10" - resolved "https://registry.yarnpkg.com/fs-vacuum/-/fs-vacuum-1.2.10.tgz#b7629bec07a4031a2548fdf99f5ecf1cc8b31e36" - dependencies: - graceful-fs "^4.1.2" - path-is-inside "^1.0.1" - rimraf "^2.5.2" - -fs-write-stream-atomic@^1.0.8, fs-write-stream-atomic@~1.0.10: +fs-write-stream-atomic@^1.0.8: version "1.0.10" resolved "https://registry.yarnpkg.com/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz#b47df53493ef911df75731e70a9ded0189db40c9" dependencies: @@ -5796,23 +5561,6 @@ generate-page-webpack-plugin@^1.1.0: version "1.1.1" resolved "https://registry.yarnpkg.com/generate-page-webpack-plugin/-/generate-page-webpack-plugin-1.1.1.tgz#82208fb599f45d70940cbd6327a7a46e77c5ee2c" -genfun@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/genfun/-/genfun-4.0.1.tgz#ed10041f2e4a7f1b0a38466d17a5c3e27df1dfc1" - -gentle-fs@^2.0.0, gentle-fs@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/gentle-fs/-/gentle-fs-2.0.1.tgz#585cfd612bfc5cd52471fdb42537f016a5ce3687" - dependencies: - aproba "^1.1.2" - fs-vacuum "^1.2.10" - graceful-fs "^4.1.11" - iferr "^0.1.5" - mkdirp "^0.5.1" - path-is-inside "^1.0.2" - read-cmd-shim "^1.0.1" - slide "^1.1.6" - get-caller-file@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5" @@ -6021,7 +5769,7 @@ graceful-fs@^4.1.11, graceful-fs@^4.1.2: version "4.1.15" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00" -graceful-fs@^4.1.6, graceful-fs@^4.1.9, graceful-fs@~4.1.11: +graceful-fs@^4.1.6, graceful-fs@^4.1.9: version "4.1.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" @@ -6133,7 +5881,7 @@ has-symbols@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44" -has-unicode@^2.0.0, has-unicode@~2.0.1: +has-unicode@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" @@ -6210,15 +5958,6 @@ hasha@^2.2.0: is-stream "^1.0.1" pinkie-promise "^2.0.0" -hawk@~6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/hawk/-/hawk-6.0.2.tgz#af4d914eb065f9b5ce4d9d11c1cb2126eecc3038" - dependencies: - boom "4.x.x" - cryptiles "3.x.x" - hoek "4.x.x" - sntp "2.x.x" - he@1.2.x: version "1.2.0" resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" @@ -6252,10 +5991,6 @@ hmac-drbg@^1.0.0: minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" -hoek@4.x.x: - version "4.2.0" - resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.0.tgz#72d9d0754f7fe25ca2d01ad8f8f9a9449a89526d" - hoist-non-react-statics@1.x.x, hoist-non-react-statics@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-1.2.0.tgz#aa448cf0986d55cc40773b17174b7dd066cb7cfb" @@ -6294,10 +6029,6 @@ hosted-git-info@^2.1.4: version "2.5.0" resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.5.0.tgz#6d60e34b3abbc8313062c3b798ef8d901a07af3c" -hosted-git-info@^2.6.0, hosted-git-info@^2.7.1: - version "2.7.1" - resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.7.1.tgz#97f236977bd6e125408930ff6de3eec6281ec047" - hpack.js@^2.1.6: version "2.1.6" resolved "https://registry.yarnpkg.com/hpack.js/-/hpack.js-2.1.6.tgz#87774c0949e513f42e84575b3c45681fade2a0b2" @@ -6356,10 +6087,6 @@ htmlparser2@^3.9.1: inherits "^2.0.1" readable-stream "^2.0.2" -http-cache-semantics@^3.8.1: - version "3.8.1" - resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz#39b0e16add9b605bf0a9ef3d9daaf4843b4cacd2" - http-deceiver@^1.2.7: version "1.2.7" resolved "https://registry.yarnpkg.com/http-deceiver/-/http-deceiver-1.2.7.tgz#fa7168944ab9a519d337cb0bec7284dc3e723d87" @@ -6386,13 +6113,6 @@ http-parser-js@>=0.4.0: version "0.4.13" resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.4.13.tgz#3bd6d6fde6e3172c9334c3b33b6c193d80fe1137" -http-proxy-agent@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-2.1.0.tgz#e4821beef5b2142a2026bd73926fe537631c5405" - dependencies: - agent-base "4" - debug "3.1.0" - http-proxy-middleware@^0.18.0: version "0.18.0" resolved "https://registry.yarnpkg.com/http-proxy-middleware/-/http-proxy-middleware-0.18.0.tgz#0987e6bb5a5606e5a69168d8f967a87f15dd8aab" @@ -6432,13 +6152,6 @@ https-browserify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" -https-proxy-agent@^2.2.0, https-proxy-agent@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz#51552970fa04d723e04c56d04178c3f92592bbc0" - dependencies: - agent-base "^4.1.0" - debug "^3.1.0" - https-proxy@0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/https-proxy/-/https-proxy-0.0.2.tgz#9e7d542f1ce8d37c06e1f940a8a9a227bb48ddf0" @@ -6446,12 +6159,6 @@ https-proxy@0.0.2: http-proxy "^1.8.1" optimist "^0.6.1" -humanize-ms@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/humanize-ms/-/humanize-ms-1.2.1.tgz#c46e3159a293f6b896da29316d8b6fe8bb79bbed" - dependencies: - ms "^2.0.0" - husky@^1.0.0-rc.8: version "1.0.0-rc.13" resolved "https://registry.yarnpkg.com/husky/-/husky-1.0.0-rc.13.tgz#49c3cc210bfeac24d4ad272f770b7505c9091828" @@ -6526,10 +6233,6 @@ iferr@^0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501" -iferr@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/iferr/-/iferr-1.0.2.tgz#e9fde49a9da06dc4a4194c6c9ed6d08305037a6d" - ignore-by-default@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/ignore-by-default/-/ignore-by-default-1.0.1.tgz#48ca6d72f6c6a3af00a9ad4ae6876be3889e2b09" @@ -6612,7 +6315,7 @@ indexof@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d" -inflight@^1.0.4, inflight@~1.0.6: +inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" dependencies: @@ -6627,23 +6330,10 @@ inherits@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" -ini@^1.3.4, ini@^1.3.5, ini@~1.3.0: +ini@^1.3.4, ini@~1.3.0: version "1.3.5" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" -init-package-json@^1.10.3: - version "1.10.3" - resolved "https://registry.yarnpkg.com/init-package-json/-/init-package-json-1.10.3.tgz#45ffe2f610a8ca134f2bd1db5637b235070f6cbe" - dependencies: - glob "^7.1.1" - npm-package-arg "^4.0.0 || ^5.0.0 || ^6.0.0" - promzard "^0.3.0" - read "~1.0.1" - read-package-json "1 || 2" - semver "2.x || 3.x || 4 || 5" - validate-npm-package-license "^3.0.1" - validate-npm-package-name "^3.0.0" - inline-style-prefixer@^3.0.6: version "3.0.8" resolved "https://registry.yarnpkg.com/inline-style-prefixer/-/inline-style-prefixer-3.0.8.tgz#8551b8e5b4d573244e66a34b04f7d32076a2b534" @@ -6759,7 +6449,7 @@ ip-regex@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9" -ip@^1.1.0, ip@^1.1.4, ip@^1.1.5: +ip@^1.1.0, ip@^1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.5.tgz#bdded70114290828c0a039e72ef25f5aaec4354a" @@ -6824,12 +6514,6 @@ is-ci@^1.1.0: dependencies: ci-info "^1.3.0" -is-cidr@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/is-cidr/-/is-cidr-2.0.6.tgz#4b01c9693d8e18399dacd18a4f3d60ea5871ac60" - dependencies: - cidr-regex "^2.0.8" - is-data-descriptor@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" @@ -7240,7 +6924,7 @@ jsesc@~0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" -json-parse-better-errors@^1.0.0, json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2: +json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" @@ -7314,10 +6998,6 @@ jsonify@~0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" -jsonparse@^1.2.0: - version "1.3.1" - resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" - jsontoxml@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/jsontoxml/-/jsontoxml-1.0.1.tgz#07fff7f6bfbfa1097d779aec7f041b5046075e70" @@ -7399,10 +7079,6 @@ lazy-cache@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e" -lazy-property@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/lazy-property/-/lazy-property-1.0.0.tgz#84ddc4b370679ba8bd4cdcfa4c06b43d57111147" - lcid@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" @@ -7450,45 +7126,6 @@ levn@~0.3.0: prelude-ls "~1.1.2" type-check "~0.3.2" -libcipm@^2.0.1: - version "2.0.2" - resolved "https://registry.yarnpkg.com/libcipm/-/libcipm-2.0.2.tgz#4f38c2b37acf2ec156936cef1cbf74636568fc7b" - dependencies: - bin-links "^1.1.2" - bluebird "^3.5.1" - find-npm-prefix "^1.0.2" - graceful-fs "^4.1.11" - lock-verify "^2.0.2" - mkdirp "^0.5.1" - npm-lifecycle "^2.0.3" - npm-logical-tree "^1.2.1" - npm-package-arg "^6.1.0" - pacote "^8.1.6" - protoduck "^5.0.0" - read-package-json "^2.0.13" - rimraf "^2.6.2" - worker-farm "^1.6.0" - -libnpmhook@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/libnpmhook/-/libnpmhook-4.0.1.tgz#63641654de772cbeb96a88527a7fd5456ec3c2d7" - dependencies: - figgy-pudding "^3.1.0" - npm-registry-fetch "^3.0.0" - -libnpx@^10.2.0: - version "10.2.0" - resolved "https://registry.yarnpkg.com/libnpx/-/libnpx-10.2.0.tgz#1bf4a1c9f36081f64935eb014041da10855e3102" - dependencies: - dotenv "^5.0.1" - npm-package-arg "^6.0.0" - rimraf "^2.6.2" - safe-buffer "^5.1.0" - update-notifier "^2.3.0" - which "^1.3.0" - y18n "^4.0.0" - yargs "^11.0.0" - lint-staged@^7.2.2: version "7.2.2" resolved "https://registry.yarnpkg.com/lint-staged/-/lint-staged-7.2.2.tgz#0983d55d497f19f36d11ff2c8242b2f56cc2dd05" @@ -7612,19 +7249,6 @@ locate-path@^3.0.0: p-locate "^3.0.0" path-exists "^3.0.0" -lock-verify@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/lock-verify/-/lock-verify-2.0.2.tgz#148e4f85974915c9e3c34d694b7de9ecb18ee7a8" - dependencies: - npm-package-arg "^5.1.2 || 6" - semver "^5.4.1" - -lockfile@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/lockfile/-/lockfile-1.0.4.tgz#07f819d25ae48f87e538e6578b6964a4981a5609" - dependencies: - signal-exit "^3.0.2" - lodash-decorators@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/lodash-decorators/-/lodash-decorators-6.0.0.tgz#4e0639ba639d738e5f4993acf54bf292cc95d851" @@ -7635,25 +7259,10 @@ lodash-es@^4.17.5, lodash-es@^4.2.1: version "4.17.10" resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.10.tgz#62cd7104cdf5dd87f235a837f0ede0e8e5117e05" -lodash._baseuniq@~4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash._baseuniq/-/lodash._baseuniq-4.6.0.tgz#0ebb44e456814af7905c6212fa2c9b2d51b841e8" - dependencies: - lodash._createset "~4.0.0" - lodash._root "~3.0.0" - -lodash._createset@~4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/lodash._createset/-/lodash._createset-4.0.3.tgz#0f4659fbb09d75194fa9e2b88a6644d363c9fe26" - lodash._getnative@^3.0.0: version "3.9.1" resolved "https://registry.yarnpkg.com/lodash._getnative/-/lodash._getnative-3.9.1.tgz#570bc7dede46d61cdcde687d65d3eecbaa3aaff5" -lodash._root@~3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/lodash._root/-/lodash._root-3.0.1.tgz#fba1c4524c19ee9a5f8136b4609f017cf4ded692" - lodash.assign@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/lodash.assign/-/lodash.assign-4.2.0.tgz#0d99f3ccd7a6d261d19bdaeb9245005d285808e7" @@ -7662,7 +7271,7 @@ lodash.camelcase@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" -lodash.clonedeep@^4.3.2, lodash.clonedeep@~4.5.0: +lodash.clonedeep@^4.3.2: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" @@ -7734,18 +7343,6 @@ lodash.throttle@^4.0.0, lodash.throttle@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.throttle/-/lodash.throttle-4.1.1.tgz#c23e91b710242ac70c37f1e1cda9274cc39bf2f4" -lodash.union@~4.6.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/lodash.union/-/lodash.union-4.6.0.tgz#48bb5088409f16f1821666641c44dd1aaae3cd88" - -lodash.uniq@~4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" - -lodash.without@~4.4.0: - version "4.4.0" - resolved "https://registry.yarnpkg.com/lodash.without/-/lodash.without-4.4.0.tgz#3cd4574a00b67bae373a94b748772640507b7aac" - lodash@4.17.11, "lodash@>=3.5 <5", lodash@^4.13.1, lodash@^4.15.0, lodash@^4.17.11, lodash@^4.3.0: version "4.17.11" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" @@ -7833,7 +7430,7 @@ lru-cache@^4.0.1: pseudomap "^1.0.2" yallist "^2.1.2" -lru-cache@^4.1.1, lru-cache@^4.1.2, lru-cache@^4.1.3: +lru-cache@^4.1.1: version "4.1.3" resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.3.tgz#a1175cf3496dfc8436c156c334b4955992bce69c" dependencies: @@ -7850,38 +7447,6 @@ make-dir@^1.0.0: dependencies: pify "^3.0.0" -"make-fetch-happen@^2.5.0 || 3 || 4", make-fetch-happen@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-4.0.1.tgz#141497cb878f243ba93136c83d8aba12c216c083" - dependencies: - agentkeepalive "^3.4.1" - cacache "^11.0.1" - http-cache-semantics "^3.8.1" - http-proxy-agent "^2.1.0" - https-proxy-agent "^2.2.1" - lru-cache "^4.1.2" - mississippi "^3.0.0" - node-fetch-npm "^2.0.2" - promise-retry "^1.1.1" - socks-proxy-agent "^4.0.0" - ssri "^6.0.0" - -make-fetch-happen@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/make-fetch-happen/-/make-fetch-happen-3.0.0.tgz#7b661d2372fc4710ab5cc8e1fa3c290eea69a961" - dependencies: - agentkeepalive "^3.4.1" - cacache "^10.0.4" - http-cache-semantics "^3.8.1" - http-proxy-agent "^2.1.0" - https-proxy-agent "^2.2.0" - lru-cache "^4.1.2" - mississippi "^3.0.0" - node-fetch-npm "^2.0.2" - promise-retry "^1.1.1" - socks-proxy-agent "^3.0.1" - ssri "^5.2.4" - mamacro@^0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/mamacro/-/mamacro-0.0.3.tgz#ad2c9576197c9f1abf308d0787865bd975a3f3e4" @@ -7940,10 +7505,6 @@ mdn-data@~1.1.0: version "1.1.4" resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-1.1.4.tgz#50b5d4ffc4575276573c4eedb8780812a8419f01" -meant@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/meant/-/meant-1.0.1.tgz#66044fea2f23230ec806fb515efea29c44d2115d" - media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" @@ -8164,14 +7725,14 @@ minimist@~0.0.1: version "0.0.10" resolved "http://registry.npmjs.org/minimist/-/minimist-0.0.10.tgz#de3f98543dbf96082be48ad1a0c7cda836301dcf" -minipass@^2.2.1, minipass@^2.3.3, minipass@^2.3.4: +minipass@^2.2.1, minipass@^2.3.4: version "2.3.5" resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.5.tgz#cacebe492022497f656b0f0f51e2682a9ed2d848" dependencies: safe-buffer "^5.1.2" yallist "^3.0.0" -minizlib@^1.1.0, minizlib@^1.1.1: +minizlib@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.1.1.tgz#6734acc045a46e61d596a43bb9d9cd326e19cc42" dependencies: @@ -8192,21 +7753,6 @@ mississippi@^2.0.0: stream-each "^1.1.0" through2 "^2.0.0" -mississippi@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-3.0.0.tgz#ea0a3291f97e0b5e8776b363d5f0a12d94c67022" - dependencies: - concat-stream "^1.5.0" - duplexify "^3.4.2" - end-of-stream "^1.1.0" - flush-write-stream "^1.0.0" - from2 "^2.1.0" - parallel-transform "^1.1.0" - pump "^3.0.0" - pumpify "^1.3.3" - stream-each "^1.1.0" - through2 "^2.0.0" - mixin-deep@^1.2.0: version "1.3.1" resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.1.tgz#a49e7268dce1a0d9698e45326c5626df3543d0fe" @@ -8246,7 +7792,7 @@ ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" -ms@2.1.1, ms@^2.0.0, ms@^2.1.1: +ms@2.1.1, ms@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" @@ -8274,7 +7820,7 @@ mutationobserver-shim@^0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/mutationobserver-shim/-/mutationobserver-shim-0.3.2.tgz#f4d5dae7a4971a2207914fb5a90ebd514b65acca" -mute-stream@0.0.7, mute-stream@~0.0.4: +mute-stream@0.0.7: version "0.0.7" resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" @@ -8334,14 +7880,6 @@ node-dir@^0.1.10: dependencies: minimatch "^3.0.2" -node-fetch-npm@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/node-fetch-npm/-/node-fetch-npm-2.0.2.tgz#7258c9046182dca345b4208eda918daf33697ff7" - dependencies: - encoding "^0.1.11" - json-parse-better-errors "^1.0.0" - safe-buffer "^5.1.1" - node-fetch@^1.0.1: version "1.7.3" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.3.tgz#980f6f72d85211a5347c6b2bc18c5b84c3eb47ef" @@ -8468,7 +8006,7 @@ nodemon@^1.18.4: dependencies: abbrev "1" -nopt@^4.0.1, nopt@~4.0.1: +nopt@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" dependencies: @@ -8481,7 +8019,7 @@ nopt@~1.0.10: dependencies: abbrev "1" -normalize-package-data@^2.0.0, normalize-package-data@^2.3.2, normalize-package-data@^2.3.4, normalize-package-data@^2.4.0, "normalize-package-data@~1.0.1 || ^2.0.0", normalize-package-data@~2.4.0: +normalize-package-data@^2.3.2, normalize-package-data@^2.3.4: version "2.4.0" resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.4.0.tgz#12f95a307d58352075a04907b84ac8be98ac012f" dependencies: @@ -8500,60 +8038,10 @@ normalize-range@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942" -npm-audit-report@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/npm-audit-report/-/npm-audit-report-1.3.1.tgz#e79ea1fcb5ffaf3031102b389d5222c2b0459632" - dependencies: - cli-table3 "^0.5.0" - console-control-strings "^1.1.0" - npm-bundled@^1.0.1: version "1.0.5" resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.5.tgz#3c1732b7ba936b3a10325aef616467c0ccbcc979" -npm-cache-filename@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/npm-cache-filename/-/npm-cache-filename-1.0.2.tgz#ded306c5b0bfc870a9e9faf823bc5f283e05ae11" - -npm-install-checks@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/npm-install-checks/-/npm-install-checks-3.0.0.tgz#d4aecdfd51a53e3723b7b2f93b2ee28e307bc0d7" - dependencies: - semver "^2.3.0 || 3.x || 4 || 5" - -npm-lifecycle@^2.0.3: - version "2.1.0" - resolved "https://registry.yarnpkg.com/npm-lifecycle/-/npm-lifecycle-2.1.0.tgz#1eda2eedb82db929e3a0c50341ab0aad140ed569" - dependencies: - byline "^5.0.0" - graceful-fs "^4.1.11" - node-gyp "^3.8.0" - resolve-from "^4.0.0" - slide "^1.1.6" - uid-number "0.0.6" - umask "^1.1.0" - which "^1.3.1" - -npm-logical-tree@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/npm-logical-tree/-/npm-logical-tree-1.2.1.tgz#44610141ca24664cad35d1e607176193fd8f5b88" - -"npm-package-arg@^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0", "npm-package-arg@^4.0.0 || ^5.0.0 || ^6.0.0", "npm-package-arg@^5.1.2 || 6", npm-package-arg@^6.0.0, npm-package-arg@^6.1.0: - version "6.1.0" - resolved "https://registry.yarnpkg.com/npm-package-arg/-/npm-package-arg-6.1.0.tgz#15ae1e2758a5027efb4c250554b85a737db7fcc1" - dependencies: - hosted-git-info "^2.6.0" - osenv "^0.1.5" - semver "^5.5.0" - validate-npm-package-name "^3.0.0" - -npm-packlist@^1.1.10, npm-packlist@~1.1.10: - version "1.1.11" - resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.1.11.tgz#84e8c683cbe7867d34b1d357d893ce29e28a02de" - dependencies: - ignore-walk "^3.0.1" - npm-bundled "^1.0.1" - npm-packlist@^1.1.6: version "1.1.12" resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.1.12.tgz#22bde2ebc12e72ca482abd67afc51eb49377243a" @@ -8567,69 +8055,12 @@ npm-path@^2.0.2: dependencies: which "^1.2.10" -npm-pick-manifest@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/npm-pick-manifest/-/npm-pick-manifest-2.1.0.tgz#dc381bdd670c35d81655e1d5a94aa3dd4d87fce5" - dependencies: - npm-package-arg "^6.0.0" - semver "^5.4.1" - -npm-profile@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/npm-profile/-/npm-profile-3.0.2.tgz#58d568f1b56ef769602fd0aed8c43fa0e0de0f57" - dependencies: - aproba "^1.1.2 || 2" - make-fetch-happen "^2.5.0 || 3 || 4" - -npm-registry-client@^8.5.1: - version "8.6.0" - resolved "https://registry.yarnpkg.com/npm-registry-client/-/npm-registry-client-8.6.0.tgz#7f1529f91450732e89f8518e0f21459deea3e4c4" - dependencies: - concat-stream "^1.5.2" - graceful-fs "^4.1.6" - normalize-package-data "~1.0.1 || ^2.0.0" - npm-package-arg "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" - once "^1.3.3" - request "^2.74.0" - retry "^0.10.0" - safe-buffer "^5.1.1" - semver "2 >=2.2.1 || 3.x || 4 || 5" - slide "^1.1.3" - ssri "^5.2.4" - optionalDependencies: - npmlog "2 || ^3.1.0 || ^4.0.0" - -npm-registry-fetch@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/npm-registry-fetch/-/npm-registry-fetch-1.1.1.tgz#710bc5947d9ee2c549375072dab6d5d17baf2eb2" - dependencies: - bluebird "^3.5.1" - figgy-pudding "^3.0.0" - lru-cache "^4.1.2" - make-fetch-happen "^3.0.0" - npm-package-arg "^6.0.0" - safe-buffer "^5.1.1" - -npm-registry-fetch@^3.0.0: - version "3.2.1" - resolved "https://registry.yarnpkg.com/npm-registry-fetch/-/npm-registry-fetch-3.2.1.tgz#e25f306dbe7f0a1e388f04a51086f09f3dec57af" - dependencies: - bluebird "^3.5.1" - figgy-pudding "^3.2.0" - lru-cache "^4.1.3" - make-fetch-happen "^4.0.1" - npm-package-arg "^6.1.0" - npm-run-path@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" dependencies: path-key "^2.0.0" -npm-user-validate@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/npm-user-validate/-/npm-user-validate-1.0.0.tgz#8ceca0f5cea04d4e93519ef72d0557a75122e951" - npm-which@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/npm-which/-/npm-which-3.0.1.tgz#9225f26ec3a285c209cae67c3b11a6b4ab7140aa" @@ -8638,120 +8069,7 @@ npm-which@^3.0.1: npm-path "^2.0.2" which "^1.2.10" -npm@^6.1.0: - version "6.4.0" - resolved "https://registry.yarnpkg.com/npm/-/npm-6.4.0.tgz#82a5a8151488d424958339561b4c0fccd33dd3e4" - dependencies: - JSONStream "^1.3.3" - abbrev "~1.1.1" - ansicolors "~0.3.2" - ansistyles "~0.1.3" - aproba "~1.2.0" - archy "~1.0.0" - bin-links "^1.1.2" - bluebird "~3.5.1" - byte-size "^4.0.3" - cacache "^11.1.0" - call-limit "~1.1.0" - chownr "~1.0.1" - cli-columns "^3.1.2" - cli-table3 "^0.5.0" - cmd-shim "~2.0.2" - columnify "~1.5.4" - config-chain "~1.1.11" - detect-indent "~5.0.0" - detect-newline "^2.1.0" - dezalgo "~1.0.3" - editor "~1.0.0" - figgy-pudding "^3.2.0" - find-npm-prefix "^1.0.2" - fs-vacuum "~1.2.10" - fs-write-stream-atomic "~1.0.10" - gentle-fs "^2.0.1" - glob "~7.1.2" - graceful-fs "~4.1.11" - has-unicode "~2.0.1" - hosted-git-info "^2.7.1" - iferr "^1.0.2" - inflight "~1.0.6" - inherits "~2.0.3" - ini "^1.3.5" - init-package-json "^1.10.3" - is-cidr "^2.0.6" - json-parse-better-errors "^1.0.2" - lazy-property "~1.0.0" - libcipm "^2.0.1" - libnpmhook "^4.0.1" - libnpx "^10.2.0" - lock-verify "^2.0.2" - lockfile "^1.0.4" - lodash._baseuniq "~4.6.0" - lodash.clonedeep "~4.5.0" - lodash.union "~4.6.0" - lodash.uniq "~4.5.0" - lodash.without "~4.4.0" - lru-cache "^4.1.3" - meant "~1.0.1" - mississippi "^3.0.0" - mkdirp "~0.5.1" - move-concurrently "^1.0.1" - node-gyp "^3.8.0" - nopt "~4.0.1" - normalize-package-data "~2.4.0" - npm-audit-report "^1.3.1" - npm-cache-filename "~1.0.2" - npm-install-checks "~3.0.0" - npm-lifecycle "^2.0.3" - npm-package-arg "^6.1.0" - npm-packlist "~1.1.10" - npm-pick-manifest "^2.1.0" - npm-profile "^3.0.2" - npm-registry-client "^8.5.1" - npm-registry-fetch "^1.1.0" - npm-user-validate "~1.0.0" - npmlog "~4.1.2" - once "~1.4.0" - opener "~1.4.3" - osenv "^0.1.5" - pacote "^8.1.6" - path-is-inside "~1.0.2" - promise-inflight "~1.0.1" - qrcode-terminal "^0.12.0" - query-string "^6.1.0" - qw "~1.0.1" - read "~1.0.7" - read-cmd-shim "~1.0.1" - read-installed "~4.0.3" - read-package-json "^2.0.13" - read-package-tree "^5.2.1" - readable-stream "^2.3.6" - request "^2.87.0" - retry "^0.12.0" - rimraf "~2.6.2" - safe-buffer "^5.1.2" - semver "^5.5.0" - sha "~2.0.1" - slide "~1.1.6" - sorted-object "~2.0.1" - sorted-union-stream "~2.1.3" - ssri "^6.0.0" - stringify-package "^1.0.0" - tar "^4.4.6" - text-table "~0.2.0" - tiny-relative-date "^1.3.0" - uid-number "0.0.6" - umask "~1.1.0" - unique-filename "~1.1.0" - unpipe "~1.0.0" - update-notifier "^2.5.0" - uuid "^3.3.2" - validate-npm-package-license "^3.0.4" - validate-npm-package-name "~3.0.0" - which "^1.3.1" - worker-farm "^1.6.0" - write-file-atomic "^2.3.0" - -"npmlog@0 || 1 || 2 || 3 || 4", "npmlog@2 || ^3.1.0 || ^4.0.0", npmlog@^4.0.0, npmlog@^4.0.2, npmlog@^4.1.2, npmlog@~4.1.2: +"npmlog@0 || 1 || 2 || 3 || 4", npmlog@^4.0.0, npmlog@^4.0.2, npmlog@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" dependencies: @@ -8892,7 +8210,7 @@ on-headers@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.1.tgz#928f5d0f470d49342651ea6794b0857c100693f7" -once@^1.3.0, once@^1.3.1, once@^1.3.3, once@^1.4.0, once@~1.4.0: +once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" dependencies: @@ -8916,10 +8234,6 @@ opener@^1.5.1: version "1.5.1" resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.1.tgz#6d2f0e77f1a0af0032aca716c2c1fbb8e7e8abed" -opener@~1.4.3: - version "1.4.3" - resolved "https://registry.yarnpkg.com/opener/-/opener-1.4.3.tgz#5c6da2c5d7e5831e8ffa3964950f8d6674ac90b8" - opn@5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/opn/-/opn-5.2.0.tgz#71fdf934d6827d676cecbea1531f95d354641225" @@ -9005,7 +8319,7 @@ os-tmpdir@^1.0.0, os-tmpdir@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" -osenv@0, osenv@^0.1.4, osenv@^0.1.5: +osenv@0, osenv@^0.1.4: version "0.1.5" resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" dependencies: @@ -9073,36 +8387,6 @@ package-json@^4.0.0: registry-url "^3.0.3" semver "^5.1.0" -pacote@^8.1.6: - version "8.1.6" - resolved "https://registry.yarnpkg.com/pacote/-/pacote-8.1.6.tgz#8e647564d38156367e7a9dc47a79ca1ab278d46e" - dependencies: - bluebird "^3.5.1" - cacache "^11.0.2" - get-stream "^3.0.0" - glob "^7.1.2" - lru-cache "^4.1.3" - make-fetch-happen "^4.0.1" - minimatch "^3.0.4" - minipass "^2.3.3" - mississippi "^3.0.0" - mkdirp "^0.5.1" - normalize-package-data "^2.4.0" - npm-package-arg "^6.1.0" - npm-packlist "^1.1.10" - npm-pick-manifest "^2.1.0" - osenv "^0.1.5" - promise-inflight "^1.0.1" - promise-retry "^1.1.1" - protoduck "^5.0.0" - rimraf "^2.6.2" - safe-buffer "^5.1.2" - semver "^5.5.0" - ssri "^6.0.0" - tar "^4.4.3" - unique-filename "^1.1.0" - which "^1.3.0" - pako@^1.0.5, pako@~1.0.5: version "1.0.6" resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.6.tgz#0101211baa70c4bca4a0f63f2206e97b7dfaf258" @@ -9235,7 +8519,7 @@ path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" -path-is-inside@^1.0.1, path-is-inside@^1.0.2, path-is-inside@~1.0.2: +path-is-inside@^1.0.1, path-is-inside@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" @@ -9525,17 +8809,10 @@ progress@^1.1.8: version "1.1.8" resolved "http://registry.npmjs.org/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be" -promise-inflight@^1.0.1, promise-inflight@~1.0.1: +promise-inflight@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" -promise-retry@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/promise-retry/-/promise-retry-1.1.1.tgz#6739e968e3051da20ce6497fb2b50f6911df3d6d" - dependencies: - err-code "^1.0.0" - retry "^0.10.0" - promise.prototype.finally@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/promise.prototype.finally/-/promise.prototype.finally-3.1.0.tgz#66f161b1643636e50e7cf201dc1b84a857f3864e" @@ -9550,12 +8827,6 @@ promise@^7.1.1: dependencies: asap "~2.0.3" -promzard@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/promzard/-/promzard-0.3.0.tgz#26a5d6ee8c7dee4cb12208305acfb93ba382a9ee" - dependencies: - read "1" - prop-types@15.x, prop-types@^15.5.0, prop-types@^15.5.10, prop-types@^15.5.6, prop-types@^15.5.7, prop-types@^15.5.8, prop-types@^15.5.9, prop-types@^15.6.1, prop-types@^15.6.2: version "15.6.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.6.2.tgz#05d5ca77b4453e985d60fc7ff8c859094a497102" @@ -9571,16 +8842,6 @@ prop-types@^15.5.4, prop-types@^15.6.0: loose-envify "^1.3.1" object-assign "^4.1.1" -proto-list@~1.2.1: - version "1.2.4" - resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" - -protoduck@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/protoduck/-/protoduck-5.0.0.tgz#752145e6be0ad834cb25716f670a713c860dce70" - dependencies: - genfun "^4.0.1" - proxy-addr@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.4.tgz#ecfc733bf22ff8c6f407fa275327b9ab67e48b93" @@ -9660,10 +8921,6 @@ qr.js@0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/qr.js/-/qr.js-0.0.0.tgz#cace86386f59a0db8050fa90d9b6b0e88a1e364f" -qrcode-terminal@^0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz#bb5b699ef7f9f0505092a3748be4464fe71b5819" - qrcode.react@^0.9.2: version "0.9.2" resolved "https://registry.yarnpkg.com/qrcode.react/-/qrcode.react-0.9.2.tgz#52f9d55db38bc2d75d63b685ca266b7d6286575b" @@ -9684,7 +8941,7 @@ qs@^6.6.0: resolved "https://registry.yarnpkg.com/qs/-/qs-6.6.0.tgz#a99c0f69a8d26bf7ef012f871cdabb0aee4424c2" integrity sha512-KIJqT9jQJDQx5h5uAVPimw6yVg2SekOKu959OCtktD3FjzbpvaPr8i4zzg07DOMz+igA4W/aNM7OV8H37pFYfA== -query-string@6.1.0, query-string@^6.1.0: +query-string@6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/query-string/-/query-string-6.1.0.tgz#01e7d69f6a0940dac67a937d6c6325647aa4532a" dependencies: @@ -9703,10 +8960,6 @@ querystringify@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.0.0.tgz#fa3ed6e68eb15159457c89b37bc6472833195755" -qw@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/qw/-/qw-1.0.1.tgz#efbfdc740f9ad054304426acb183412cc8b996d4" - raf@^3.4.0: version "3.4.0" resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.0.tgz#a28876881b4bc2ca9117d4138163ddb80f781575" @@ -10394,14 +9647,13 @@ react-lifecycles-compat@^3.0.0, react-lifecycles-compat@^3.0.2, react-lifecycles version "3.0.4" resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" -react-mde@7.0.3: - version "7.0.3" - resolved "https://registry.yarnpkg.com/react-mde/-/react-mde-7.0.3.tgz#7653385fcbaa8bbafe4610860fbbea1ae0c0ab1e" - integrity sha512-WVPrvS5MR3LFBXZoeMDWC2DF63Oeew8SBa+ry3ySe+6bT05A2uUot25Oiux4ACwDBDnlqmoezpvFvcS9y6dcHg== +react-mde@7.0.4: + version "7.0.4" + resolved "https://registry.yarnpkg.com/react-mde/-/react-mde-7.0.4.tgz#bb80e848135fac98693ad59429a49cad7f6dc178" + integrity sha512-uph77+5bJvgqpbSSmXMlr3yfs1a0+5XfiLAvQCZv/h3nuGq3KfpPzk6X3GcW1qo7wkggxTQ/0a8a4EE98z4cSA== dependencies: classnames "^2.2.5" insert-text-at-cursor "^0.1.2" - npm "^6.1.0" react-media@^1.8.0: version "1.9.2" @@ -10544,46 +9796,6 @@ read-chunk@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/read-chunk/-/read-chunk-1.0.1.tgz#5f68cab307e663f19993527d9b589cace4661194" -read-cmd-shim@^1.0.1, read-cmd-shim@~1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/read-cmd-shim/-/read-cmd-shim-1.0.1.tgz#2d5d157786a37c055d22077c32c53f8329e91c7b" - dependencies: - graceful-fs "^4.1.2" - -read-installed@~4.0.3: - version "4.0.3" - resolved "https://registry.yarnpkg.com/read-installed/-/read-installed-4.0.3.tgz#ff9b8b67f187d1e4c29b9feb31f6b223acd19067" - dependencies: - debuglog "^1.0.1" - read-package-json "^2.0.0" - readdir-scoped-modules "^1.0.0" - semver "2 || 3 || 4 || 5" - slide "~1.1.3" - util-extend "^1.0.1" - optionalDependencies: - graceful-fs "^4.1.2" - -"read-package-json@1 || 2", read-package-json@^2.0.0, read-package-json@^2.0.13: - version "2.0.13" - resolved "https://registry.yarnpkg.com/read-package-json/-/read-package-json-2.0.13.tgz#2e82ebd9f613baa6d2ebe3aa72cefe3f68e41f4a" - dependencies: - glob "^7.1.1" - json-parse-better-errors "^1.0.1" - normalize-package-data "^2.0.0" - slash "^1.0.0" - optionalDependencies: - graceful-fs "^4.1.2" - -read-package-tree@^5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/read-package-tree/-/read-package-tree-5.2.1.tgz#6218b187d6fac82289ce4387bbbaf8eef536ad63" - dependencies: - debuglog "^1.0.1" - dezalgo "^1.0.0" - once "^1.3.0" - read-package-json "^2.0.0" - readdir-scoped-modules "^1.0.0" - read-pkg-up@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" @@ -10607,12 +9819,6 @@ read-pkg@^4.0.1: parse-json "^4.0.0" pify "^3.0.0" -read@1, read@~1.0.1, read@~1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/read/-/read-1.0.7.tgz#b3da19bd052431a97671d44a42634adf710b40c4" - dependencies: - mute-stream "~0.0.4" - "readable-stream@1 || 2", readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.6, readable-stream@^2.3.5, readable-stream@^2.3.6: version "2.3.6" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" @@ -10646,24 +9852,6 @@ readable-stream@^3.0.6: string_decoder "^1.1.1" util-deprecate "^1.0.1" -readable-stream@~1.1.10: - version "1.1.14" - resolved "http://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "0.0.1" - string_decoder "~0.10.x" - -readdir-scoped-modules@^1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/readdir-scoped-modules/-/readdir-scoped-modules-1.0.2.tgz#9fafa37d286be5d92cbaebdee030dc9b5f406747" - dependencies: - debuglog "^1.0.1" - dezalgo "^1.0.0" - graceful-fs "^4.1.2" - once "^1.3.0" - readdirp@^2.0.0: version "2.2.1" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" @@ -10975,33 +10163,6 @@ request@^2.65.0, request@^2.81.0, request@^2.83.0, request@^2.87.0, request@^2.8 tunnel-agent "^0.6.0" uuid "^3.3.2" -request@^2.74.0: - version "2.83.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.83.0.tgz#ca0b65da02ed62935887808e6f510381034e3356" - dependencies: - aws-sign2 "~0.7.0" - aws4 "^1.6.0" - caseless "~0.12.0" - combined-stream "~1.0.5" - extend "~3.0.1" - forever-agent "~0.6.1" - form-data "~2.3.1" - har-validator "~5.0.3" - hawk "~6.0.2" - http-signature "~1.2.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.17" - oauth-sign "~0.8.2" - performance-now "^2.1.0" - qs "~6.5.1" - safe-buffer "^5.1.1" - stringstream "~0.0.5" - tough-cookie "~2.3.3" - tunnel-agent "^0.6.0" - uuid "^3.1.0" - require-directory@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" @@ -11060,10 +10221,6 @@ resolve-from@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748" -resolve-from@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6" - resolve-pathname@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/resolve-pathname/-/resolve-pathname-2.2.0.tgz#7e9ae21ed815fd63ab189adeee64dc831eefa879" @@ -11108,21 +10265,13 @@ ret@~0.1.10: version "0.1.15" resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" -retry@^0.10.0: - version "0.10.1" - resolved "https://registry.yarnpkg.com/retry/-/retry-0.10.1.tgz#e76388d217992c252750241d3d3956fed98d8ff4" - -retry@^0.12.0: - version "0.12.0" - resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" - right-align@^0.1.1: version "0.1.3" resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef" dependencies: align-text "^0.1.1" -rimraf@2, rimraf@2.6.2, rimraf@^2.2.8, rimraf@^2.5.2, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2, rimraf@~2.6.2: +rimraf@2, rimraf@2.6.2, rimraf@^2.2.8, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" dependencies: @@ -11275,11 +10424,11 @@ semver-diff@^2.0.0: dependencies: semver "^5.0.3" -"semver@2 >=2.2.1 || 3.x || 4 || 5", "semver@2 || 3 || 4 || 5", semver@^5.4.1: +"semver@2 || 3 || 4 || 5", semver@^5.4.1: version "5.4.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.4.1.tgz#e059c09d8571f0540823733433505d3a2f00b18e" -"semver@2.x || 3.x || 4 || 5", "semver@^2.3.0 || 3.x || 4 || 5", semver@^5.0.1, semver@^5.5.1: +semver@^5.0.1, semver@^5.5.1: version "5.5.1" resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.1.tgz#7dfdd8814bdb7cabc7be0fb1d734cfb66c940477" @@ -11385,13 +10534,6 @@ sha.js@^2.4.0, sha.js@^2.4.8: inherits "^2.0.1" safe-buffer "^5.0.1" -sha@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/sha/-/sha-2.0.1.tgz#6030822fbd2c9823949f8f72ed6411ee5cf25aae" - dependencies: - graceful-fs "^4.1.2" - readable-stream "^2.0.2" - shallow-clone@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/shallow-clone/-/shallow-clone-0.1.2.tgz#5909e874ba77106d73ac414cfec1ffca87d97060" @@ -11474,18 +10616,6 @@ slice-ansi@0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35" -slide@^1.1.3, slide@^1.1.6, slide@~1.1.3, slide@~1.1.6: - version "1.1.6" - resolved "https://registry.yarnpkg.com/slide/-/slide-1.1.6.tgz#56eb027d65b4d2dce6cb2e2d32c4d4afc9e1d707" - -smart-buffer@^1.0.13: - version "1.1.15" - resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-1.1.15.tgz#7f114b5b65fab3e2a35aa775bb12f0d1c649bf16" - -smart-buffer@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/smart-buffer/-/smart-buffer-4.0.1.tgz#07ea1ca8d4db24eb4cac86537d7d18995221ace3" - snapdragon-node@^2.0.1: version "2.1.1" resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" @@ -11513,12 +10643,6 @@ snapdragon@^0.8.1: source-map-resolve "^0.5.0" use "^3.1.0" -sntp@2.x.x: - version "2.1.0" - resolved "https://registry.yarnpkg.com/sntp/-/sntp-2.1.0.tgz#2c6cec14fedc2222739caf9b5c3d85d1cc5a2cc8" - dependencies: - hoek "4.x.x" - sockjs-client@1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/sockjs-client/-/sockjs-client-1.1.4.tgz#5babe386b775e4cf14e7520911452654016c8b12" @@ -11560,34 +10684,6 @@ sockjs@0.3.19: faye-websocket "^0.10.0" uuid "^3.0.1" -socks-proxy-agent@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-3.0.1.tgz#2eae7cf8e2a82d34565761539a7f9718c5617659" - dependencies: - agent-base "^4.1.0" - socks "^1.1.10" - -socks-proxy-agent@^4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-4.0.1.tgz#5936bf8b707a993079c6f37db2091821bffa6473" - dependencies: - agent-base "~4.2.0" - socks "~2.2.0" - -socks@^1.1.10: - version "1.1.10" - resolved "https://registry.yarnpkg.com/socks/-/socks-1.1.10.tgz#5b8b7fc7c8f341c53ed056e929b7bf4de8ba7b5a" - dependencies: - ip "^1.1.4" - smart-buffer "^1.0.13" - -socks@~2.2.0: - version "2.2.1" - resolved "https://registry.yarnpkg.com/socks/-/socks-2.2.1.tgz#68ad678b3642fbc5d99c64c165bc561eab0215f9" - dependencies: - ip "^1.1.5" - smart-buffer "^4.0.1" - sort-object-keys@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/sort-object-keys/-/sort-object-keys-1.1.2.tgz#d3a6c48dc2ac97e6bc94367696e03f6d09d37952" @@ -11596,17 +10692,6 @@ sort-order@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/sort-order/-/sort-order-1.0.1.tgz#d822b8cdb90ea6a9df968c4bd45987cf548199e6" -sorted-object@~2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/sorted-object/-/sorted-object-2.0.1.tgz#7d631f4bd3a798a24af1dffcfbfe83337a5df5fc" - -sorted-union-stream@~2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/sorted-union-stream/-/sorted-union-stream-2.1.3.tgz#c7794c7e077880052ff71a8d4a2dbb4a9a638ac7" - dependencies: - from2 "^1.3.0" - stream-iterate "^1.1.0" - source-list-map@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.0.tgz#aaa47403f7b245a92fbc97ea08f250d6087ed085" @@ -11658,30 +10743,12 @@ source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" -spdx-correct@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.0.0.tgz#05a5b4d7153a195bc92c3c425b69f3b2a9524c82" - dependencies: - spdx-expression-parse "^3.0.0" - spdx-license-ids "^3.0.0" - spdx-correct@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40" dependencies: spdx-license-ids "^1.0.2" -spdx-exceptions@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.1.0.tgz#2c7ae61056c714a5b9b9b2b2af7d311ef5c78fe9" - -spdx-expression-parse@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz#99e119b7a5da00e05491c9fa338b7904823b41d0" - dependencies: - spdx-exceptions "^2.1.0" - spdx-license-ids "^3.0.0" - spdx-expression-parse@~1.0.0: version "1.0.4" resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz#9bdf2f20e1f40ed447fbe273266191fced51626c" @@ -11690,10 +10757,6 @@ spdx-license-ids@^1.0.2: version "1.2.2" resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz#c9df7a3424594ade6bd11900d596696dc06bac57" -spdx-license-ids@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.0.tgz#7a7cd28470cc6d3a1cfe6d66886f6bc430d3ac87" - spdy-transport@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/spdy-transport/-/spdy-transport-3.0.0.tgz#00d4863a6400ad75df93361a1608605e5dcdcf31" @@ -11747,10 +10810,6 @@ ssri@^5.2.4: dependencies: safe-buffer "^5.1.1" -ssri@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.0.tgz#fc21bfc90e03275ac3e23d5a42e38b8a1cbc130d" - stable@~0.1.6: version "0.1.8" resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" @@ -11818,13 +10877,6 @@ stream-http@^2.7.2: to-arraybuffer "^1.0.0" xtend "^4.0.0" -stream-iterate@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/stream-iterate/-/stream-iterate-1.2.0.tgz#2bd7c77296c1702a46488b8ad41f79865eecd4e1" - dependencies: - readable-stream "^2.1.5" - stream-shift "^1.0.0" - stream-shift@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952" @@ -11917,10 +10969,6 @@ string_decoder@^1.1.1: dependencies: safe-buffer "~5.1.0" -string_decoder@~0.10.x: - version "0.10.31" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" - string_decoder@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" @@ -11935,14 +10983,6 @@ stringify-object@^3.2.2: is-obj "^1.0.1" is-regexp "^1.0.0" -stringify-package@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/stringify-package/-/stringify-package-1.0.0.tgz#e02828089333d7d45cd8c287c30aa9a13375081b" - -stringstream@~0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878" - strip-ansi@3.0.1, strip-ansi@^3.0.0, strip-ansi@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" @@ -12104,18 +11144,6 @@ tar@^4: safe-buffer "^5.1.2" yallist "^3.0.2" -tar@^4.4.3, tar@^4.4.6: - version "4.4.6" - resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.6.tgz#63110f09c00b4e60ac8bcfe1bf3c8660235fbc9b" - dependencies: - chownr "^1.0.1" - fs-minipass "^1.2.5" - minipass "^2.3.3" - minizlib "^1.1.0" - mkdirp "^0.5.0" - safe-buffer "^5.1.2" - yallist "^3.0.2" - term-size@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/term-size/-/term-size-1.2.0.tgz#458b83887f288fc56d6fffbfad262e26638efa69" @@ -12137,7 +11165,7 @@ text-hex@1.0.x: version "1.0.0" resolved "https://registry.yarnpkg.com/text-hex/-/text-hex-1.0.0.tgz#69dc9c1b17446ee79a92bf5b884bb4b9127506f5" -text-table@0.2.0, text-table@~0.2.0: +text-table@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" @@ -12152,7 +11180,7 @@ through2@^2.0.0, through2@^2.0.3: readable-stream "^2.1.5" xtend "~4.0.1" -"through@>=2.2.7 <3", through@^2.3.6, through@~2.3.4, through@~2.3.6, through@~2.3.8: +through@^2.3.6, through@~2.3.4, through@~2.3.6, through@~2.3.8: version "2.3.8" resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" @@ -12178,10 +11206,6 @@ tiny-invariant@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/tiny-invariant/-/tiny-invariant-1.0.3.tgz#91efaaa0269ccb6271f0296aeedb05fc3e067b7a" -tiny-relative-date@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/tiny-relative-date/-/tiny-relative-date-1.3.0.tgz#fa08aad501ed730f31cc043181d995c39a935e07" - tiny-warning@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/tiny-warning/-/tiny-warning-1.0.2.tgz#1dfae771ee1a04396bdfde27a3adcebc6b648b28" @@ -12478,14 +11502,6 @@ uglifyjs-webpack-plugin@1.3.0, uglifyjs-webpack-plugin@^1.2.4: webpack-sources "^1.1.0" worker-farm "^1.5.2" -uid-number@0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81" - -umask@^1.1.0, umask@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/umask/-/umask-1.1.0.tgz#f29cebf01df517912bb58ff9c4e50fde8e33320d" - undefsafe@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/undefsafe/-/undefsafe-2.0.2.tgz#225f6b9e0337663e0d8e7cfd686fc2836ccace76" @@ -12524,7 +11540,7 @@ union-value@^1.0.0: is-extendable "^0.1.1" set-value "^0.4.3" -unique-filename@^1.1.0, unique-filename@~1.1.0: +unique-filename@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.0.tgz#d05f2fe4032560871f30e93cbe735eea201514f3" dependencies: @@ -12579,7 +11595,7 @@ upath@^1.0.5: version "1.1.0" resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.0.tgz#35256597e46a581db4793d0ce47fa9aebfc9fabd" -update-notifier@^2.3.0, update-notifier@^2.5.0: +update-notifier@^2.3.0: version "2.5.0" resolved "https://registry.yarnpkg.com/update-notifier/-/update-notifier-2.5.0.tgz#d0744593e13f161e406acb1d9408b72cad08aff6" dependencies: @@ -12660,10 +11676,6 @@ util-deprecate@^1.0.1, util-deprecate@^1.0.2, util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" -util-extend@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/util-extend/-/util-extend-1.0.3.tgz#a7c216d267545169637b3b6edc6ca9119e2ff93f" - util.promisify@^1.0.0, util.promisify@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.0.tgz#440f7165a459c9a16dc145eb8e72f35687097030" @@ -12700,19 +11712,6 @@ validate-npm-package-license@^3.0.1: spdx-correct "~1.0.0" spdx-expression-parse "~1.0.0" -validate-npm-package-license@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a" - dependencies: - spdx-correct "^3.0.0" - spdx-expression-parse "^3.0.0" - -validate-npm-package-name@^3.0.0, validate-npm-package-name@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/validate-npm-package-name/-/validate-npm-package-name-3.0.0.tgz#5fa912d81eb7d0c74afc140de7317f0ca7df437e" - dependencies: - builtins "^1.0.3" - value-equal@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/value-equal/-/value-equal-0.4.0.tgz#c5bdd2f54ee093c04839d71ce2e4758a6890abc7" @@ -12809,12 +11808,6 @@ wbuf@^1.1.0, wbuf@^1.7.3: dependencies: minimalistic-assert "^1.0.0" -wcwidth@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" - dependencies: - defaults "^1.0.3" - webapp-webpack-plugin@2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/webapp-webpack-plugin/-/webapp-webpack-plugin-2.3.1.tgz#c158a9c80f219423e27f2fa4db8ec8e8fbdcb3ef" @@ -13076,7 +12069,7 @@ which-module@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" -which@1, which@^1.2.10, which@^1.2.14, which@^1.2.9, which@^1.3.0, which@^1.3.1: +which@1, which@^1.2.10, which@^1.2.14, which@^1.2.9: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" dependencies: @@ -13135,7 +12128,7 @@ wordwrap@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" -worker-farm@^1.5.2, worker-farm@^1.6.0: +worker-farm@^1.5.2: version "1.6.0" resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.6.0.tgz#aecc405976fab5a95526180846f0dba288f3a4a0" dependencies: @@ -13265,12 +12258,6 @@ yargs-parser@^8.1.0: dependencies: camelcase "^4.1.0" -yargs-parser@^9.0.2: - version "9.0.2" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-9.0.2.tgz#9ccf6a43460fe4ed40a9bb68f48d43b8a68cc077" - dependencies: - camelcase "^4.1.0" - yargs@12.0.2, yargs@^12.0.1: version "12.0.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.2.tgz#fe58234369392af33ecbef53819171eff0f5aadc" @@ -13305,23 +12292,6 @@ yargs@^10.0.3: y18n "^3.2.1" yargs-parser "^8.1.0" -yargs@^11.0.0: - version "11.1.0" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-11.1.0.tgz#90b869934ed6e871115ea2ff58b03f4724ed2d77" - dependencies: - cliui "^4.0.0" - decamelize "^1.1.1" - find-up "^2.1.0" - get-caller-file "^1.0.1" - os-locale "^2.0.0" - require-directory "^2.1.1" - require-main-filename "^1.0.1" - set-blocking "^2.0.0" - string-width "^2.0.0" - which-module "^2.0.0" - y18n "^3.2.1" - yargs-parser "^9.0.2" - yargs@^6.5.0: version "6.6.0" resolved "http://registry.npmjs.org/yargs/-/yargs-6.6.0.tgz#782ec21ef403345f830a808ca3d513af56065208" From d4d9f5b2abe16adec57177b5c36ab548be42a19e Mon Sep 17 00:00:00 2001 From: William O'Beirne Date: Tue, 12 Mar 2019 17:13:04 -0400 Subject: [PATCH 35/72] Use new logo + update meta tags (#332) * Replace logo in header and footer. * Update some meta tags. --- frontend/client/components/BasicHead.tsx | 16 ++++++--- frontend/client/components/Footer/index.tsx | 31 +++++++++++++----- frontend/client/components/Footer/style.less | 8 ++--- frontend/client/components/Header/style.less | 10 +++--- .../client/static/images/logo-name-light.svg | 26 ++++++++++++--- frontend/client/static/images/logo-name.svg | 29 ++++++++++++---- frontend/client/static/images/ogp-logo.png | Bin 19762 -> 29336 bytes frontend/client/static/locales/en/common.json | 3 ++ 8 files changed, 90 insertions(+), 33 deletions(-) diff --git a/frontend/client/components/BasicHead.tsx b/frontend/client/components/BasicHead.tsx index 2e8363e8..50424f19 100644 --- a/frontend/client/components/BasicHead.tsx +++ b/frontend/client/components/BasicHead.tsx @@ -1,6 +1,7 @@ import React from 'react'; import { Helmet } from 'react-helmet'; import { RouteComponentProps, withRouter } from 'react-router'; +import { withNamespaces, WithNamespaces } from 'react-i18next'; import ogpLogo from 'static/images/ogp-logo.png'; import { urlToPublic } from 'utils/helpers'; @@ -8,17 +9,22 @@ interface OwnProps { title: string; } -type Props = OwnProps & RouteComponentProps; +type Props = OwnProps & RouteComponentProps & WithNamespaces; class BasicHead extends React.Component { render() { - const { children, title } = this.props; + const { children, title, t } = this.props; const defaultOgpUrl = process.env.PUBLIC_HOST_URL + this.props.location.pathname; const defaultOgpImage = urlToPublic(ogpLogo); return (
{`ZF Grants - ${title}`} + + { {/* open graph protocol defaults, can be overridden in children */} + @@ -40,8 +47,9 @@ class BasicHead extends React.Component { {/* twitter defaults, can be overridden in children */} - + + @@ -51,4 +59,4 @@ class BasicHead extends React.Component { } } -export default withRouter(BasicHead); +export default withNamespaces()(withRouter(BasicHead)); diff --git a/frontend/client/components/Footer/index.tsx b/frontend/client/components/Footer/index.tsx index 081d59c0..4d265513 100644 --- a/frontend/client/components/Footer/index.tsx +++ b/frontend/client/components/Footer/index.tsx @@ -1,11 +1,12 @@ import React from 'react'; import { Link } from 'react-router-dom'; import { Icon } from 'antd'; +import { withNamespaces, WithNamespaces } from 'react-i18next'; import ZFGrantsLogo from 'static/images/logo-name-light.svg'; import GrantIoLogo from 'static/images/grantio-logo-name.svg'; import './style.less'; -export default () => ( +const Footer: React.SFC = ({ t }) => (
@@ -23,13 +24,13 @@ export default () => ( -

- ZF Grants is an open-source, community driven platform that helps - creators get funding to build a better Zcash. ZF Grants is owned - and operated by the Zcash Foundation. -

+

{t('site.description')}

- + About @@ -47,12 +48,24 @@ export default () => (
); + +export default withNamespaces()(Footer); diff --git a/frontend/client/components/Footer/style.less b/frontend/client/components/Footer/style.less index f312d54e..4f1d79fd 100644 --- a/frontend/client/components/Footer/style.less +++ b/frontend/client/components/Footer/style.less @@ -39,7 +39,7 @@ &-title { margin-bottom: 1.25rem; - opacity: 0.8; + opacity: 0.7; transition: transform 100ms ease, opacity 100ms ease; &:hover, @@ -51,9 +51,9 @@ } &-logo { - height: 6.8rem; - margin-bottom: 0.5rem; - margin-top: -1.25rem; + height: 3.4rem; + margin-bottom: 0.75rem; + margin-top: -0.75rem; } } diff --git a/frontend/client/components/Header/style.less b/frontend/client/components/Header/style.less index 67dcc3d9..84a85343 100644 --- a/frontend/client/components/Header/style.less +++ b/frontend/client/components/Header/style.less @@ -1,5 +1,5 @@ @import '~styles/variables.less'; -@header-height: 68px; +@header-height: 64px; @header-transition: 200ms; @link-padding: 0.7rem; @small-query: ~'(max-width: 820px)'; @@ -44,22 +44,22 @@ transition: transform 100ms ease, opacity 100ms ease; flex-grow: 1; text-align: center; - opacity: 0.8; + opacity: 0.7; &:hover, &:focus { color: inherit; - transform: translateY(-2px) translate(-50%, -50%); opacity: 0.9; } &-logo { - height: 3.6rem; + height: 2rem; width: auto; + transform: translateY(-7%); transition: transform @header-transition ease; .is-transparent & { - transform: scale(1.4) translateY(20%); + transform: scale(1.4) translateY(18%); } } } diff --git a/frontend/client/static/images/logo-name-light.svg b/frontend/client/static/images/logo-name-light.svg index 25f4ed2f..23f34bf5 100644 --- a/frontend/client/static/images/logo-name-light.svg +++ b/frontend/client/static/images/logo-name-light.svg @@ -1,8 +1,24 @@ - - - - - + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/frontend/client/static/images/logo-name.svg b/frontend/client/static/images/logo-name.svg index 0fc5c158..e940c962 100644 --- a/frontend/client/static/images/logo-name.svg +++ b/frontend/client/static/images/logo-name.svg @@ -1,7 +1,24 @@ - - - - - + + + + + + + + + + + + + + + + + + + + + + - + \ No newline at end of file diff --git a/frontend/client/static/images/ogp-logo.png b/frontend/client/static/images/ogp-logo.png index 30d320f4be3f9418d1138caf2ff5500b41430bd3..11342558f00b29e761b27a3d17351153d7f05df3 100644 GIT binary patch literal 29336 zcmeFZRa9GB*FIh-RG>&pfl{DoaVcJ$7I$|o?zFf|X-jb@I20}J!L1YxZXvik1PPWP zzuoV9&l&IeF8;Uwn{$(lk&(Uj+H|%pIztB#ndh66@Z*dpNQ(5^DGE-LJcM z4_)ce1K<~HA4+}T3(ZAM`u)A~VX~cj_r&hWO1#tXMB86@==od|+HoxY7$fc;+Iy#Q zN!23i&)<gqGHlik&xK`OsOcr@|_Tak(!#il{jCv0_{D0(CF3XKB`k* zHf13LwvcjToFyddZgn`1Y8Id8a@m@HLdcvT%>En$U+f;*e|?ZK#Ebd6*{K#GTuRs2|4x=vp{{Q(YijeZB+y3a-$N#$5pV2<+4gdG; z|NPX8g+D3azHR;XzwZwW`;K+%A5)?X3^c=hWJ@mS%1!pqF#@xbT>kM?_+lMZz^tOk z1ztw|a||&R2>(BylyX@Im{l(>Iga!{$DsVVLH18$;eRZ>_pZz=Bs$Zzt^bCse=~u96Rdv|tbg;? zf8oHtDC=L8^)Gn+C$I4THz&~d1nmz?5h#pK!etTn853JBQ^@NzC+GO7+wHZNtdO_H zv-mjkyEY7d=k@Qx4-2*aqCY4V079k9JbQ-@{m~4&N{htfBCP|2=THdS1E^IFIZ7fT`P&%V%^o=YLOEUe|aEAORK_iL3!S`ha zK(bh&0|PGyi@~Zl@)p#UAqy9i@ew2kvdZ`r6zs-PFB#1OknfZ-1UAH(VVHmME+5+g zA_Uc&{E9j-@GCkd*7Jn&1>;yjc#}S-d7sILp(5=%5BMt*fpvnB1${p{(C5E5@{uUo z=h-{mBLd*AF~xy&ezpt9PhOjixN)rdV2%2k)5r}7sGqdeC~+u_59ak^SL(0nlD!5H z($AHgsz;RgPl6g6-g?1rBXQtIcem71bl#sMh?E$JNlQzbE=gJ4oSb?F4}f z9iDIkY(*&?R6JD-`5tR5Q#5WdKF3W7x8gi}v1D|C8`>8qNyKV^A7kc>|M#*b0F0Bk zm#Z}?`Xda@Wwy0O7+UZHc?AUpmATmQPG;>QCTK!~)GcyPncwH!UM}tEgS9RDr(;sGu0)<&@bN3hI~N@1X}h`wjsqOZoBZ<>cl%@SCft z#ifgSC~;a1+k4$?41hr5A-Mg;ns+)ze1z10kzGGnI^slr2np2UpkbiX#c-HOT}}2m z8?+*alE_?ckO3B(5u)aupRcN|E*xR)0qyB;WA}>$we3rvv*^fR5wlUdIkp=fHk`82 zkZ9tzNBaOzZ*w|BQ|;XC+`gBC{+H1^7y&CNkuc5zh67{O`mM@(9d8W#ldnw{sh=Dq zk7ZU|8a69H379M;_0FRMs&)l3|UY1*^iHpMW#M|C~Sro(PuQdp3e8T9;MbV z(JQ@R7xr;Ib>{>B%@2|>isdj!@PKNV*RaXs~Y){@qj3@;^bzMQ;m3JEW zVB9y0@9)RqdxSsP^x^r=ee_3{w4ez(+iI`t&FFe!%4q? z=PvG>Lcj#{N=HqL1DZOsy>{I0fAPt^fWZYUe_Au3Khk-vQtV37pUfDqJ$1w5QlLFI zS9c7YhO3JuHzUn}g)_p|+04klmx}%|aHUPHH8XG}wMx!!9-}Fj!+HJ`;XU?>^D37$ z{u(PUO}{GB?w0?Lt0W#*?82JdhM8CE zut&fDICuUG1 zA^n#qzt|kfeCsh&wNjl=BNsW{VRDNNzBwc0RwJ1Um*vJVT59U|N|{1b{k`$8`E}c) zAVI+oEw;`Ns$I5lVO3_$_6yaLy!P{yTf>=kIbM@N|9Nf^z@*5)9=7Sn&-;wUyxWjX zZiG$Q1ulhA14|U`JD)T27QSAkxnQvVJmaODbhGti<)s>vr*P4WQ?^QQ z^QrFN$Kl_g5dG}!bl)Waxoo#2{y{Xu-4EDe`Ob;OYTcJj#|3h$LtIm_u`r2t)4O)A z{0R6|$&#-wG>Tl1E}+7I3h-_5ufvJ^$1UU^JF^q4692EE_knFv2L;VO6{Ez!MCZ79 z%Z9Aw{^=>>$m}OU&3OY?a+)68%Io0?1^j=PuKDh)PD(vvNV>qhzuoIADd3JzYTR@Lvw|m7T7{;p+mgFSLXh>Jt-Q5k6VD+Ocbjpd(;IO#{V8g*h+?Q#h0$w1Xv0P^RNd!YynJJ;Mv0bTDvuLYJ+yU# zEAp+*P2^C9ermmE-)TPNogeCe?_>gRseh#_BzTH8D*S&IEU*MfJPd_fTyfDb)SkT& zyzxBA3q2Wo2p?$kL#Y*SRzu}_GGco8$Pxx5@>ZFOGO=F~U-I((w6 z%{R!LCRvygN-U6h2;Oms;a~GA(%k(yuXplukSCM>RXug|zH)@jC%)EGfe+VjuUvXu zcP`cFZO+B8W%qCqULZx$VH5L8$H|7G+JH@61P3G;~aGv*4m;>R< zdAZ{j-(sR+{$8 zONUQU{KSf)(<0~FFnPy@%;b}FPH4|2zl_w1@2(yxk|eXtcQ+YF8Y`ww2cc$Mu|pT)SBj7<-MopKFEvt z*z9qUcsRnD0(3+g0>Zf)Zez^bXjdll=XOR)v~FndL=k~4H`8f3omYw7I1|w>>wQ!e z?Z*WXSBLb{4{AYCp=>0G!I}}sOcuRSqT1>BJoQPGS22`bLmFKl_lPUPq#?iVf4OX2VA5wf6FBoy}Vy^pBwX42snDki3 z*&Ii%1rpva3q2HO*S|lzGZF2^nCY=}Cxb=Elwi7)fi$gJ?cS(SHTEiqYrhzti%a&L zEHeDD@Tfsg3|cpwyc2K-e=`SLX!d~g6VMMj4!Z++N-hwqqd)EgRvRW9#iOHFqJuet5JR>D$Bkj(o32evf^ipb<@J0e`#lAa3r?XU z=k4{-%Gjz-H4cPNpESNgz$lnv2c26mJY%a#D~*)#$S?d6k2%YyQk2McO*}fr3s%E zjaa@^sn)O|Ioq&SK3_w-5sm2z!Apa&=GBi0CUr?gBmfBXtE=FFZ8{yww2`9OwTKuE zjyaIX48Kv@2eu*_;OTJ~_#TQ=`k^W6JWsi`wEs{gHa1VcQI$rJXMbxhZ@cKvP;y<( zSRbF%UI+Ijs;0F(s4bd18reuizR52YH#1{>qz{Ht5r$uCF*_G>C} ztk2kdjA{ON2J&L@2dV%ht;M`XVGz#x<+|bGzx6uF10r}g8taV{0X8us_0y+M!L`~o z$L{+_rDVdk2XU_+#!;^$?v#D%@>fK(yczf#QtWAHhGqTCkQM$rBezLq52-EoPlT$S ztrDP`8T^Hv4oWG_j6F54%F1Q;lnXXaHYyU?+h5U~ZVtt+h=;tTKDA6@(KTPrrcx`g zpH0bV?Qt8$y+O8h)|x$xBv!=1Lo<|8U~l-}nF46o$Jby_0J8Y|(bWgp zbdI67dc#>46<~rwu7S5SZ*EVQ(oX`b=*mTw_LPfsYm&C1aoyoUd(h!b9!O0>tf<>; zofBNdNp{M1>Y!L;eQ&DnYvHZH>-9O=`%fS71yu_vlRAnk1&}iVM8RlKE-Z+UL9aHs z1$7$&&PUw&u0IkPoqdz1%b5kb=2wzMqlsb7M17gpKqNos%FL+Cp(P#ZR=O07R?tt| zGeu`$-klP_B}s{s9%Q5pC?9zp4V~HZNaN9&tN_qN${AE4MKqw?ez8H7*J;uGYVe-% zlhj41!b@J;eMouIe(nfGFFXo#FbD3>6!v*ixEAMo-9X0I!cF?=d>{6j$tU6W282Fe z9u!M08Lo${Uq1RF-c!5A$FhT+2oOwl=t9bNW%PAaCLq8U8CFm3y+ku)J~cDbGgnZ+ z)kktW9g>l}U8d3B#qE{DeqlE-&2#umi_Kl(q&xi?O-S5^#qA~;r{$+~vMMjEvz{+9 zJ3K`V^QwoH+}tIhkVVbxwW>HKCQs@l_iGDRvY0*zo*fq{|e^F?(}LB&riRe@e4V$GcKl6xD+8%}*;esJb2)vcEFSQVzHazRl?bFKi%oCQhJ( zB%B|vJyW<)?EHJu?%VP7N}ieOMs4;*u?|SHF{{Hrc~UBOt1+U{$7!H;slc0!&a(z@ zjyh0m835R%Z|$PK1d0Ml$L)4i#UAFY(AZ&hv!0f_PT}X-*cSr>yaM@b8J6=a=bnvK zeH{9!zSA=6+%}qyBhQ38<(k|O5_BZIC4qyB)*}&UUzxQ^8zRWg5?hYuxiV?h!6rs( zHYL&&@h4ENhIPWdfJqbgd1cXo?@zmEKfsOl@JuFO0E}GMH{7I^u5W#RXgaIRJrmo= z1@VV?1Nu_V3;>k>QV?kLaiSN80r2Zcbe0_gq?rVEFs;g3QtEis8U##!Gxci0eC-D)X&!Ac3mA4C-9b@Hz=i|8Pe4Jlzc+a#O+hxVQ^7XA?V7I+|C*!lzw4M{;mosXB zcjLyC12Nj|oSy!G$?JU$6a;wC1&%1hFH^J(rOQkXR!%cIpQH>D4&R^GA_sio`sA5Y zi9?`$ug%HbMZ4+(sv4|;IrZ?q!!Dx2JaA2yVXCHPJ&--CYNf4FW;(CWhIxy@nA_ub4m=N!nK;+yuG3K~>m(wiK9-=mm6ej||ZY zUWFXajB;X>KQ}=hCsJR(I!8~%uR zAU?9Fr@bsHG2Y5o`PR%Pcl_u68PvNkp`T#mYZSX|H>sn7nLK>4=>R+@JP}*(w6wVp zMF(!1mMP5}$(~hpKb)r;5uI3!hryzdtwriJOn00sRw*22O)b_K$3yx;>#_{_vOioE z_4tU$=hE!vsreQ(`s2eXJ?tGvUS=VmoQ}~*n?uVpsymZ^j2B+m`3pn$BQtIlD>RC# z|71Yi9J6K+nP2*y5uD|ckv>D#scw}5RyO*)>J_{q~ zm{mMhqU7XE(#nDs{We_FGHFAD$RabV`==FpAa zNf8aKcV(VzhaeW+ahdNPtr`u70*oK7QK1~u2UF`Q9Q?N^VIUUvwEu{A{^eOJ$G*DL z?sB77c!Oe`T}XQhjLA)VfNSzC*%yh1)xl2`h`jvdBc)yWay<@#phD z>g`q3i+&Ctc=VmJyFM=w`R<2UL7jAOW#v0_FB$vab)kWUO4?>G@0}^2+;imDznG3m zzeKj3MYSxz6|c;Ya4_$2KB!ry^E2j+h?IxfOE>S(eeO3c6|noxcQ-@%3b5*eDBZGw z$ehRZ`%D1)jbI_roaj^W%YABMr^aaZer5X5EgjGG)GccI%Uh=#8u=4rJU(~X)r%%PHcWHGJ z8BZgPTcmwwv{n*iU|f~6Er}H{kSW|O{P@`a72kA&XQ!F=txmPmF5u0I=8lwJVAXop z*17I2P9$aSY3@ktfsYJgmG-VVL4ncj+_4S7%l zD*3L#C!)d|P?nW}e9@*9jd(3-*J{^kE^4Lz+ zUhDR)^~`n?^hC#3w2>uRW*0?QDBRpA2kZ!=L?>7M*Bqg?S-W9TQAu9dAY?5(QLrwc zx@<6u?_vNX+`eZf030oAA)34N?V1xULO&_L%`C3@yj|Mbr>(;wu|&trEZnFJ-v`qRueuTzywHN-@95f3+A8LLHPJT)#_4R z562ghlD6;Cc{mdMj|z0?@#8m2l@rO|pu-ZIbIf7q47zoK0}TWl>O54<6FHLDlInyeQ_{HuUuzGaDrBbj!B)(!-Mbd znAZsjx&Oec;0&po__}#7nO8Y$xl-sF~xDr!R8-o!ck-D}CkE{3Az_kp!gO zCeO{?W=X5Je34rIkp1BluXk+DA-TsuS)FaTUi1qN1H>?Yw>Mgb*8D=UVRkgmj3I&} zMfuwkkeAC=9}3@csTWKs6aF)NtY&Tu%JaH$gtyAvnr9M)N zbOG_@`&+AwBHAn&TQ^SakW}?PAdrRe`_Ch8mAM}1FW=&l32C^7IaSJce*U_CCgLPF zlv;;A{d@?2@v#3qvsc609lqNb_`EVWq%Vkj+4t8Q&)X|vmUbC83*sqMBQCjcK)dUv zjGEebV%kx2GRCwZmU1SMGr<7J1iC9{L_O8h(M2br zUO~rSd4T|qXWHu?RTd@P&`o7BlX3|NXIoe*65YGw5`D$}J3f85oDf~DdQ1>(>?_;@ zF5O`ypBSo0WYLxT12neVb4@bZy=mSR245UpL~VOXzU!a}I=W)skA8+~(#w_}F_NPG zLTbOWHqENPo>-nCSxBK?N-QJvf$fL70c%oy_NVK#_GvN{4GU*>DAZAPWt&l8qwN5vOq45Dcmsz-ZuR0v3Sz~P7zr|&NQ|T zU%t5PSD~SrBzJ#BH`T-we3mA4FV7>4u?>5h+pP6%J{S4>AJ)9rvAJz0=DoOI&qp6v z{gHpV%v;wWbGbgmuxg7Mk4RIy3Jh-js+6>4 z{%YsKb2XhB^#eR|u%k`7Ku-p_F-Wtq_JG)b`po4n{kZq5@s(pu2Tiny$~{hX`d&{5r|MwJUU|(~krOX!CPVE+exthoDmi!>K{xv%Oj)n4FwO*^T zqISnl9)6qfF0}iSP$bTVTKhY3?crC}Ge1Ke_UcibncgCZ9Ta%79fhmyjWRX0%vy9h zCp47iPYAapUTU$4r@s?tsE-GK&AjJfEAFIV9Xo`cI;L6JuzmW${ducT9xRG6=;<3S zOn#T1(`O_fBs!i)2f`Th9^$>KR4A28EKZtoTa~E328a%eGgZ}D^fC%3QbbW$be+v! zBSEUWy!e`7372ok`-+!5j?b`$8cK1zKQ-;#Ok1kA@84rXNd`-1C@z0FdEX0?fN~d7 zE=K~eicime4=c!V!ACBCK+}E)vN@C{G!Z5H!~)wXERilnfhaM!xsLb-i;xpUCIJno z%h?VYG3^f5S4}L8VvMJgHO+AwLFrAuA=b(xM$;EQu~zXZkbfk+w9=go4hjw_5K99q zpFAlD{V}%nBg?NmIFwb~jKEO6b*rQzM9R$is~M0BlXGcsPhthpDZ)Vy^+oI@HMOT= z6Oy-OpSbPMRFy2QVWKdilAqX~N@am7p5m4?n*8`xphwnj2P!%kpW7~wn>Lb2<@7T3 z4q5jL$8h9MItmTt0I|;Hh#P4|TF&{Q7A_ooG}fzwkHgn65a=07kp+3IAHz8umdar^ zHWaW6kF%5=pyavbgSk)Q4&(wK_|{kERh_sl5fQ2|ZdZDCet3paOWc%e$GrE9K>#96us^TY?5=7f#HI zFu#tDUV4ps_KcQ;am)^y%1Q5L{8=%iq0J?>I+|O+2W4 z+=|V^{r&T>}c26a}N1(5pLTh_M>7M-YO&DoqC6pi!~K0h>LcGvC39}{s8{W-a(Ia)L<#)i#k8Bucl$(t%lS^k_s;OxqLJz5-fb^pX+zlz=LB_VM@MeFyS|e~ z(9^Tad$`an-Q;n=A`fEWH9Q+2gyd^Nh)w_&Tc(+QE^TpS@Y|GAn=Zmv(2ePmD}Jz+ zFJ|sJcdR&7>-qLphH{9HD2<}k`zO3?)-9LLTM*pX^mLZBp4)BR@af_&UqIx6#=q16 z@|7h-SV+JSY|ro2rDHuGm7`77?C!4%cG@C&CFt8)bh@4r5oc{K7A0%dpJ~wZe7iJd zB-D{&xO?A4shJhamWHNAwkKjbg+kbQW_+c@-vwu9{D4q?ueQ2c5}Wvot4g!)73Uh; zCVRF0&NCPgfwq@=62da?d#$YsdAzMCQ+YR>DSPwj-VMIL{E)tSaZd$waR&-TEVR_( z=8dFA-|Rdbt0ED&=8^}M$n7=!I9ZIPkmn^VV%ty>}&otybsTVlz zXDOODa`2Q!AkNev-hWq$_uCudTMvOxWIEdx(}3)2&3fIv)>nu)KOVhN7IW*@FTYXk ztuhO-^24%bm)yAtE;WYUzXgT#XeAv-pcfs`(=T(ahcWYbvR(W-CnVD_Pz@RZ)Fw8 zpDR|I{Kc}TRmb>zcdV%GHYtp6p26#M^Qg6NUSQa$A>JgIU;S>H zWwVDaae>|EYFo9S#PGqhWn%>YBsf}hwV%iy`U9Jg&EDo&LBO3MeOIJ?sXLM1Vx{~R zNTP7Msc~CPFZdx&WodY-jP2*AwECL+gXQ*`q`|7uyIepq@<{^?W)8DW>rnUBj{{5RM1mV{`KoI2_VH zi!lI6{kPShqjXsq2t0=+fB0pslegebNUo!lDTASF`ZI6zImHKcl4i;zdq&@kQ1_>D zPUx};|Oe7>4aWO!cggM|1GKS_C8n9J*9#ccu_P47QM5EFu7mRjn=4gyS z^_fffZ`j3D>=wgV#dRnJ=%gMzA!E^^&u<*v018?Oz@{*`OoUU>#kg|DlL=+|FA5I% zr;AEovR#?*%1#Pk2kS9|TE<_0sJ5(g6ma>T(L*(t&QqctZpn-zQS>px%`Vv=HMA|p zeYMKr>U~&mhVC;M{;>Y);lW%>V-2fln25fh$1sch?oc{^jq!+|-uQR?Qd&k1)g;{p zrE-CK@4Pw%q+9BfmqHr5lSPp3FZg5^AMKyLb9fuo1VNVeJ1zQe33D`Sgw`))30PEH z%X!OLV<)%j>$A1r$#T8dr#_ob!Fe%VR&%ISCSqm1F&q08i%_XGxWz2ILs`^6YgbKJ z>(v`Nx{z&`C9MQSsT{6Wal-AKcV(pUE8Dd$ij$VoV+Mp;ghQ3`je5%=@E(JkoU96K zwPAt!C92?WPvVyfM-p(^TlyCCMHOKSfT&aN?s`TL10&P_+-rnFOE5J@R?53|dLpX0 zXu>q;;NjpxCo@ICyE3Jbk$C!>{X}og0D!|xiW**Q^a45ycSUJG)WKo8{hGR3M;Isy@IM^w0s7d={)@#EUN{ z6ugX&R`;LyrR}z9aF%BG0>b3az;&C-yx`Mf>rKLso%T-i15mpnEw~T==19zJWU%Oe z;YV{EeU=iy&7lEOV*fTY8|?U&f_EQ6w>4ZCz_uPLK=X;?Tp<_Rq8DI_O^dzclUR7` zGWbY!y+S(5H=0^@l;1iudqlf=z$RRnRVr1y(~`Z7*#}B>ss<;)?)J5ws>_AKb7Rp! z+;rhD`pKm2b4wcR_{ zo}5vARHeKbWFqFm~3z>AY zS?~ZA9Pi7NYtL0cX&ldYQ)lglb<+0xQ2_4NPd^?S>nYpK>H)T7(UFrjhXu=p+IqRf z5*b(5TH4pTAWhf_Zu8GyU%s@W?I{``O)<+kzQX4*;x+2^H(%DO;4JJ*=cCgc&r9$W{2K9gp`zsoDUf!%?ah<@OUMiut+^iNs z?#f_L^R0{WfD`g+S8ol=2M#qpt08j7OX`>v-pnj11L~jV!{!Grxt+T;DQl@2-Yt{K z-ledxGPaW2tGV%r$~xO6OE-cdb_|Sa%gEY1Lg`>n(E0m^QDnG|CVoH2wfvw~oChpg zqPYz*TXvfaaiA8G~)*1g-^^VrH8gbTQA`)POcu3?MrGzhX zkYdbooavCbF~$H&@lcq#=FS+jNMF199I~6Tnz9JqX$`pS`%uM++Wopl$e^N)hV50m#b@tXp9hN=nn!MTG!oSceZi!TtlRn%)%aaDYx5KTFl5Y7 zqFPMm-^_*$FoTmyImvihdCLpschhI+Lqa9)N6Toi$`7p$xpr(uhh0xVCfxU^JyJSjGXyc1V%`_l5FQ~~mh z)j5lPMbw^4ouG$a{j2=NG6z_3Vo_UFEEk3nZDCg9yn?dxS22||dXHCJiWa08@864A zjSGDYAlR8`1_31%uAdIJ;|PD78=bU21dO0MP;yRLBho7KuRU8cJr8cIe^a7e5hvn= zI059`cprkgdQK$w&QqJ^oohHSVMF<@DV%@?LRZ}|ozp9>=-?V=J^tJH8g^6cH&^Qy zx208~_URD&As%^c-Z79M5E}L{CBNq7^>~9fQuXvmPmH2#+)9s>w?W@%XBt+6h=L~S z)ON{dG|40uPq7q7+WrFc`n(2Gvp;T1jczQx8eO#Y^(rPCsT=8l!+QAg8YJe^Wyf*vSudtr-JelM?j`SE_#l0|=~n;x=mqoOX4&6sIy z=ho}Qxp10T@ceOG;Dh?6+Lg63ecAYLXTpuxanJ7ipy_}deE=>uLE`sRYrN&s5?7X19Bpd%nG1oL>>}C5i5Au0!d241%dZM z>E{5soxh3HLO#QFdNCkeFXnD}st}~@bC$GM-n^JZ7jfrR>U(j(2cA^S|3OnZn*7B-+vJavH-)AbvYd z;Xqa+yLZ$={0kkx{qcT}A3p=JXDyYLdf7RQS=n8{m*5_Ti&`r2+0**fm5W9-{?Lkm zitXyJi%FzT#k9&ow?FHfuj+09DUC$?$rif_3nn6l+ivl_^IXCURyH;z$0Dzi!E2Xq z#txQSNBAblPo5Dqx!y&L3p}U{a!IaATcmdw3Ny(uI*Ds*JnM7mvq-16%S`x&gST{R zT~R3~d(TmT1bjB?bZZ1+Mf1%V8TVTBj|{v*Du8|}qWRJDEiCI35oS3Fi7!GkoJX)K zH$&^23+Z17f3- zUDVD*p(NMboe;gjl(`$6;^li}om zreiS>@iq9%w+@Ou=M+boT+&iqrQZlNhuNc4J2|nGc9#_MgjtO{~NWtv( zfmEu_;S|(6T;!M7$CWapqxvjBvk!UsgP$bR=N1#PD_@ZV5Y+PVbcz#m5f|i?*~HiA zy4SLC?l)(IevZWtew(%3$u4@Mgl*1b?6US6D|3gx?af}j_WCKw1;}wwUqLs>@`?1^ z+}xYu;EXPDz+VaV@UQz6sdtO)ijJN>Ng$8(D>OFNZSze5z_8UL>}xaH@LAuE`I_n} zd0M%k^x^jKJHYM|6)kCuopO1(4ru_*r+{)OPO(JRDuzGQ9~dE^^EnhrK0DeG+cz)j zXum73;b!*R>z$JW(iv6?p}Ix#A3jBoJPu zdK0v@wYr?%E1MVR4N4^BQ7_0N#9EU-SRd~?rk zqF{>1kZBd@XEI?jizQ5Z^P=^+s03M#@wbD5AkJ0J{YRlSz`GDA~3&i;o)3aI;;Y8J*O8o z*S{H0vYO$IyU7{N?(Mrg#>Q`bw__}>2uy*_p1n6;ru+eNo@f3C42DV*rW}g2%Gzg0 zovJfl68RHYai8jdY4qG*ab6f04ylF4@gXC`z1`io&Q@cFz2B&R_5w0C6yj4L-lEUe zuL`gq)J^;$4hdauuP-}LY%_3P@B6S*g)n_z^H8sk*w~MzT_Kez)k}{+Th*7~(raF4 z$z8#MeTYeL7cvHZJ!xYn)K$)PpW*Q7s5C7?SCu(TDNC_pJGHbe$({~&PNlaKX%C;G@y2qD;#Bme1up z=2YgE0nvmE(6N(Zn{+#u3;^OHhl9oKLW=f_`ITbmvQabMYrc8U_$~RY#woXAH^1Ke z9%v5^se)qs1m3r=SVYfoOwX=lGYoTJNv9hlPCj#8JNN9Mr9?xY?Ki3&^W_qg*dvOP z4(93-GXO;Qt7tF$e0M<_(E8cS`e{7VLkHS6<3IPbdjvq-pvLTgMnS$Nb(aq+b9z&| zr~v1RDo-o*FgaTaOgWjJgUh}DWmH9NqYo&t2gx`W9O9y0=Qrb!QvP{26+SnbA++?a zH<-4L`P(hG!d;)8SP8`<<0F>Ymo!6;0+d%Sd3S^M8>X^3BhXzDB}RePp|sK!W4~iB z8N+?A+zUIkP92cm{cf@6ij?^i!R#5-zWXyAusXX!+E>C%{r&yUC+o7I5fRTbh<+M%07)E+u+CUcxB2{+(373YA1C=)cMn{PKV7`4klo?5U~!;H z{2KKq6HUUXi+stK7<6AGE-x?PJ{JzJ-MrewRuqL$%!x~hUd4l&my_P=-1y|2_Fr86 z8*^CzM3-h*F{_1glmwb%D+#AmLLh&797H;P%nB-{7%VR0++^s-)@hR^M32bU53PpFH%X3NA z*!v4w)}|=l2S+|g$T0GL*M{5_tlAxDtB;Hh3yZ6^fOs{N&dt%j8Pl=d?LSuze~_a^ z)Y^>v70xcdMt9M_MI#L~oB4xsd%iy5l4Q4@Y^h7C_1U$V*b_@D0y(@_7Ym+c>Jg~C zCAyi*89BF$@wC7NR;=N!H|x^vJJ2*~R2Kks6#fI2FVhYL))&7;vUzpaQ@D}zWC)g~7_R7J&p`gu_Xw&}uXv?*EE+zkgE$OR7tPKT zbkp$F|0c|XW27C_Na`XpL5e4n`4f?qGW66-OHXr&zRRlcjzy!=4d5189Oi^Ann;30 zZW(Byw$r~g*;|iVd^ug;dWb7V7x?fA*$GX&9lU%~CYuR3kWqO!#vbB>VlpovC{>=` z0Md)NO7?f0mVFG3r|YpZGxKWm$?vi!#7kbP58cRan@NlQoDd1B+nbi52~V;fmlkik zI)8X|*vwJwwI*6GLC@P?j(-SE_jAC^OU<0rAwLQCrNeGpz7yr0uV9F{1JAl$wA~^H2r{^ql6~jo6;h<8b4V zz#`-0qveMU8fn>_L>R5--P-E3SRwK2Lp*G6Rq@}@0A!<7r~?b3XTwx((zLY2qh!M5 zD<`GKn(GgY_Rhky;2zfv?H*7JL7_Nb$GBjfB}AH{g?Wp|Me7la*e6{hZHrCp>;oM>S!;NlqyCoh5r&${!YoftwA7cA#zJ)$5Uaj7A>`ZH zp0HielG^EX$>Ql2rXN|yt~C0%-AKevdtv=!IL-3YM+?ffPw;_8ACs!U#j+dQpeuzs z0nMwRs~&LmAaeiwBat{V#9lH1?*pe%}lkrQ2?x zd7G_&UqKUi7V;?=sKk;=#fkuT*G6Ar8)oh=9K5@lDD8(5DV*=V_)+`mE_xAUDCqG` zEncC{;xHX{@_Q{hj1K-7l#^04SLZi4+0?jVnseh@qwF+hU#r9biEOrd?(t|alSduw zv_uq_@kbG&aObC2>@Xp%2&A*CGwrV@{L`2M-kiOiI^Ku`s(=B-&ECdFe|nk77(c${ zuZb{97;&BBvJhftpNs^d(`9w@7*^qt_N4YL`r6b9Y+Q!G)!I`7cECR_Ym|iT+yl&j zmdy$k`%DW5IXQ)dOyQ>fIj2^V>4|dn` z_6~kC;f4`W)Z|^5{m&JyHMRmUds%hBo&U~QnO)zOT0=Envqvw4w4&X819BZDmI|Cf zo0C6pAZXAu0ayJVi8~l8XeLhFD0OYJ1O0vhxxpyWVDUA0#go#iJNI)U$~d4bWKCq$ zV8>^suewM*R@4(tKDISKuZ3`wi0{EMU;T3IxR;uLGiH%BD+-f(Mj(FrDKIaO)@!F5 z*LhxsP2y8@$K|mo1b7R=`(p8MA~yUXT*m@yEiZb-wbmF)HjTTCG!Cn@WgxkJ(3_D9 z$g>|kibw?+Bg)HQaiW{GuH1(+Qs!8`4_2Hplb(&&eba$?OwKQF{c;k7{5?AZriKYIG7b-d+c+8tZsHdSy#VmJ<5^K zP?>+7{cd`%;`~v{S?k$6eD=y>%sqwmzUU!)R|pPAv)3qi!hTrTXon7>-nUIwEA`3b zzq@o~G{ z08*qYMT*iq^ezITG(`}UA_OVYK`Dw9A%qq}5sd;yRCIPU4wN)yyog2B<27@GAsSTJkIJ_|~3>Zz=p^M7)D3__F$%6iKUmxNKo zp7DHU5XT7ve&Dt$e|AB^X@55 z$UQHaCT2Gk)E$T==+MvKa*4YseQ#*_V9CV@GG%^}%%hLkWbXipsM+A%bZ`#kF!}cE zT7rWC0AYC_Q)3d;049*XsF?ibg-fqUpm{FZJE$?D*fNz&!KB7=uL%@)CVwNiy7e6K zSZA0K#Wmh{aiA}G(uQY!s-uraEuSHcSqBK`BkWXsUD8uwto;D`e;zVYo?C{2AinE* zMlYVgX6n~T}f!DPq+trOlUXPAr~uTrfN4vyqB)~Fft;%k=z>cC{7!&7DSo}PE;RrSP$m8y$iDmd84?qwP zU5%K;G$OYtVghLPMJ8;3SFO1uTu2d-1J{tI>-aQf=n`b$k0K3lB?Icvz%SUpSSHz= zo=ZiSoTxXQ(pX2S63o(qyK$n;g*KU$JpoIX; zzHX18jlV6A74ZGiR12@Tf&$sw#(+I^WLR=UN&O9AA4UbpyJbIufb%D} z1s)@)bzyIRJlhBTV;Q$s#nE&H8M0yG{_@jIM)$SOLi6=98pjark5W2Dxhc4Lb<-#! zA^=;Ql z07cBYv9PTXU7+ybm18wAF){TJMdp_uv(YTQRLKVl$izmD-~uPmV=fg&lF0y|hGMYH z_H+~w51DS>tryKg^s0)$uH z`%GDBp&lzbpjWfbF$y}My@i}RQ)IafR>Myf!W=7aYtD3MdD>)enSpZ9q-4y%rmK_$ z^`*09V~ZEz2yO-J$@6q{i>HxcwwRNug{TQ`I@7;Xo~x;4`tp}qm+4Tc2` z&;T*Dir|Rje8jpOpKi$3gqjJO_tM!209__EvY3VDx7vAWB_7N0>pC~4YdYb~8Aa_u z>9j~_@*~y5TBG&Ro|jLMn^-}y?YN}fxmXs-r3M4IsHFtpxlpsBSH25%_(0Gl2`Ism zgju-@Cs|V&cwhpV*={YXNY`$kol5%RXq6_BflrY5m&`jMlL>ZVzl2riP)Wf3Oufr! zKe;T_;S!Jf;;ihVGM4TJIib|gA@7y6u2bxdepD3XCJX|^k8y4Q5lfSD?$QTxH3D*$ zWlo8)$wHF`6wKV%qez{XL32sqmTGRH_ANd+Tie}Z8b-lsrj%+>MHMtil_Tq%NIj(; zFFA=Km-oWv?`hEATwhHrEYzC3d*R6}>U7Pp+g6wj%Ejm+^;!z4&GRcCIDX`wG?WA! zszF31X07@HheomFbS1r_Gx3#z;w{W*A8DkMGu5Jx*|1zAWXt^Ytzg6FaoG8hWiEbx z*OsIP&6`zMw{{g{4ncH52}%7UC(Qv!HuNHjXyRxztSSr}$+YwR&W$~#>B^FX-Q+jJ z$+*@c2vH^!&_Ht)p}kB~x7kR1gGK1*kYrW2|Iz+)Lj`kQGjhEOnrrw*W&+@?2Sr|P zJ6JrBmpWxB9BwETyisonFjmu%`hy%-RiG%p+j5E_ON1jbZRW0y4Y~g`px=|+%%#63 z2L^Vtn>hj4_t#X_73c=@7^JcTzbR86Qm|9cOYB2E zw$y%SOQxOqMX!Igu`_0?&DSXDc1eg;bpQmKcSbDu|=mCPLUkvjd(-Y0vFW^qk=xEfYRDUKv^d#$FkZhp)I7 ztY()NhkcfHUf-}#?gEI|G}>L;DG_dzv8Q*zdkqPic`N0>D)I%{21@;V+3eIRdq`1q;!wPqN4~K< zIn*b7BgE2N-*;_KOX*IlAP7Oz9abD2Aa?cqcCj-`ERQ4ojzgN5K005=RJ`M-t9yO3 zYT&kfxx3g%PigSo*IJ}pE^E)sRbS(caQAG1yw15x9K?E3nXgTaYES~&<0!{31r~>b z)_gU(&hMJm;$`sC(bKI;NFNRpS|T*DDqD}t2?C3gr7yiE`~z=ru-Y24w|U!D_TSB^ zQ^eFGGHtm_8|{5W{a<^cq6!bODMDN3q-(yUy~1ceVO<^-+(=k_|66c<^mLM8qr$}$d z$-8#DCXFQ^UEfM{i9)+?<^`6TesUxby-|W$@YFxQN7PfQp~QB--pJ9}M(0G`GQYYs z(1b>!z||{-h=5e8;L;Mw7>U_+-Vxdm!S`x_Zb_X@#u=j-MCqxI9~ftderF(6IJ$ zGJ|%lBgh+WF6CcWa9Ae5mzNl*yJpf?x#lK=- z=cd5u>$+Oi=sFrzhnffq{&JD;WpWeyDqmr2$L?ZMHdRbq<0-C&$OI{p3jbB z>$CZjhZqW^xN8oAFhjl7F5)1D5*dbO^78F^rfR;!YKrh5)4?lTi|>($kIt$X=DoJ% zq@x%v6-O^?27=pPW zz@F{?G{sp}0zzTFUHmG`3f2p4Q*1T!Of(P7(+m`f8L*l291bC^p z0a^0&LBk(=(l|dy$2Y)C*bCqm68EO8i zkN>CA05HJD~6$B)!C^<>aNY0lGk}&|H1O)-%5+&!%C4-^_k({|i z$ysuSSx4>n>i?&!=S|g2P1V%1y9@fK-EEqqq>s<2s6Q)+x%yoHSjg3rnVyBoujt6gK6$ymV5FZZ zQLcLaZO22n$91u6qiJVSVqvkrzu3l*)u!o~$1acfWfAXjRNb)Q$!ZrqquV)Fclt?C zhc3MJIPz9M=h0yy5s}OH?ie0q`tXR{`G_HFFW%qVIG}eVQu=Znb@Kk*ch__bFT0$) zSZ?<0770q8`A4@}bnkp(4ouH zS&tfYXs&2`FLjg%)SX!mbOFArJMh}q;S*ULr? zH2D71a!U@Fm(Mmk({cVc%bIk!2DDO-#NntDgLDBSK3&f~@{+W`n+% zjX~1Ghs{=GEGG4r-n4x_GNH?x;1+vVLe(a)MYyDvpjB)AtFG86o7~micxS!F?i$js zBBsNZWDy~LS7X!en&9?WvB(xpW8FJ3=_S%k8$3?UT3dDM>5F?~ zf=Av}9d}gdsv_Hb79+6`{(YM8Y38|f(&a!oR%3#(V^+oz{J6FV16_SdyeHdyP5T3j z3r%HgA{PY~8^n%2xoutZw!MsnDkYY%xWQU;h2Qnd*$mX(p)X{o@(f(3rdbFsA6AJu z(cwX69qs$wPNRyA$ zR^hiC=B{$I#8*GEH_Y)Ab^XcFvZK0B4of^Z{qhCfX;Rr2`B#qA5#&Cbddo0IcAi^r*IJroC~T=p4inznh`C~P&gqn^*W+6k6<@Mz3qHLf z;eF8(b?Fexi=Yt&OO6O)ix&;UVw-gR1kv7EpRn5ayJx9{51qqL@NK)V5;H|7Eq{xi zDEQ^sj~p>4A15S>$zMyQ?0iw;6Bwfz%O9I#L?7-|c#kJ;ujQQ)hZCvOk&+7<{+WWD zaiy)Ct%6gxS2+oe#XY-SeOLR#hYJ#{2JDF_xz{P5B5KsX^w;=4p~+TGXYD#M>#2+5Q20Y@}u6tp^{je4Dq}8Aye8>Nev@TIk+L~8IVo)gahC|@u zyP?N)=LBE#-0%++?G;K%Qjg(Hba@~9P5Ij`{kyCb`t*9?`sli{kQ;U6b*y@!4F(M% z-=f}^Gw+D`UpAzhyjdHFyR3Ajn}(B)lUX_#8$uk?{FWs|;Hvwh{o=z?pJYC%J&Svr zC6i!zU;D%Bk28>Tf}$f-cApq1e!v-fd3?bz#65$ChdpJTq8GhA^zT|4zmVvbatSe%lsx}12s zl3Yx&^vd%I1}1$bhYb0Q0ae2Sp@NjY%s$@vfxgLnt(Nyj1ghK_zFm?O?X4|K?Pq2$ zbxpU2b+EVjbu_k{x4Fg(cf`%!n4a&NZKG)K?jmk$ZnbEsw77RGh9dsL9Q)Mm%_l~B z7tD*kW`F%z{4VuOI~^|h(^Ty3uRV=VgRK&lm`Kv`2d+1cd5BitG&3;_*HP7W8X(}v z;~Wj6yhU-V_5)A&N+Uxf9#b6|!5hXm$KPBL*KfGjz}rC9(0yv{d<|27akk+ICj(EK zZJJ3MUf0R4{VvC@`7R4{3G*HfQVy)y%|iY{3Uf8{Ax2iUFB-O%tr|S38gB>4N|{Tu z)``|1>b*zw!uFBViSiF_y=O+JMv7WR+HeYHP}=wH$=ThCrT z?G&-TIXpd9Jeb#hYJ7ZFVXAX7W0-PUV_~mHxSO^&>6792?k0ywizMnAmMV*+q04eX z^#)yRwY!wN-8&9=Z;yOF^6f~}(eUFZkKkSmUp|P@xKA8n>M6U(<2^@vZZm=44nE6(tvi7cC_Wif=Gd`e5I%ysFImZqysDuN0ETBCX|S;5m1) z>va(RhnG_?uiJomC5uqRa0F9D4!(cZEK+AQ%J8#&bAX{hF;j)>@-32c zC$6VOD9)>}(-55QdUd&#^|5K<)wa#m*M47ze7tkj^XPN=GyPRvt^Ib%_rnJZ5>2t0 zt|9Y5d+pq--0LNomm(u1xddk~&JtUAa9l@E^(A9(yN`-wTA&ALekA5gR!*pl2alBx zT`Di;(_$0gbl>C|S|mTIck<=27itmMOWRa?e2Y638HH*a=6i~DH_KG@6y#M`6Fz9T ztl`a4e{=iL7m}Z0QM$MOf+w42lV@Sh7N??gRVz?~wT!_?X$9T4@OfZyBfZBa!S?ZV zZ26b+ALW6$Rk>ZR+dKAiJF-!-Zl#~&J{nin>}TuVi*DD~$Q@5wk4}tYHuDPa<_IW= z&lgeERm1Ds#g+6$;-n9Bw#3c7rG&p;Okl$`pKYFqj^qw?MVHC7Y|XeVZ;fP>F^nJ^W)_oIjL#^_EkUhJA6r)z@h;oWgczi0OKFxbwV|^nuC7>@@#Q#l$R8 zx7rTn4i1luIHG*Sesotd3loaIZJ|+GKDJ&JS;ymmH;Q+|fy*I!NW5}m!mVqs9+6ARf|`Qm(x0PwFBlM4?rN;=gudHNdY_cgoFCmOfOQzu_H7UH92gDE#{7OZcHn+Em@JwMu{aH}Q?c$p zy{fCTEI1-u5MUQQ745_??C!=*grbViq7EI4rPaCe3%v8D%nX=+%Q;m(P2oN}&i&i$63w@gu+z zFFPZ{c31A>bu~<{dOTS?`_0I3C+}6CyCkRzA}LA9LJ3LNjQg+4iSJE*F^lGBDvm=H zO|qu%e%@*HDIV5ZCC1Ar*TmJk%&&6*+6pw{l|#~vIM7-pdPxa|61qx?^= zSC^m1RE;d}=tgZz`Q39Yn>FAz%1&%|B=U(?Qe&CgcnMdgebJ zM7>l+zVPRof6n~JgC~H4z@OhY`s>VpI{0TQ{@m!#ng4X~&;0!Bng2Rze>=T@d+?v8 z_g}C1uaoxQbN=sv`nS{jw+H`w&QYj;jo!Zv{{N2i|D81Qw+ZJ@`2Fql{_Vm4GjsmL zX3!RrzGZ%P_AR^Vwnu*C?19nI(aX!rwo@%vDEVEKNl8ge5C=$y9Ee+5`4D zbJj44gSDZ&yu1RlURLwoT+gP%DET)^>gxUSC2++aHFfm}gXXA=5GJ+3TPUF$FmJlJ=FZN6r6vOl%VgPc2!Ok)_wIS9<{QOsZa)3`AE2eIc`ES@Nm#%ybM4;=%z`b}1Z(J+N4;YJo|@ zL?ea)o(`^al9*UYPjAdUadRbcxdEj9IhSXhch#g-6;!@SLhgtmWXb7f;gX=7o4l$tuL#BLTY z^)M_fOiWTz0gYy+z4DX**4l2a=P~?QURjZIbE_bt=1=$aB?vImiWBndNRgwpTj*Dr z$6-UGqqPdYIXXJlH#E$8Q-lqa*zJr|y1KX9;R?R;A=MmLNK`2*qp6vhpMS@7 zxt^BWdhAu>>|#eskQWhUDte$~;OBI1IZT(-&6_t>OYJkn#KkqXl_@yPJ_z|H z%d0abw$oHZ=R-*7UH&+re)ldt$kou z-PpxQ_NJ}9y}%sDVLn*aJKk&oh|Ht?OHGXlTw+_M|6{N zb7hq>)gK!eBoWv)y%cPHBz>*>YsFNapjGyKd@FP4Rnba-PA%Q>@-v*^xp-<#Fq zJw8AFEtp|@Qs5v3F6Z3ee-fF8PI7jWc-XTku%DFVP`itd96%5{ zZBHlJ&G!`?Y+Jyd6cZDZ#i9o~3eDBFHaB~0)!w{$Q{cE_;JP&)x($yQIp_fCv~6=~ zl(78U0wZLlzB0!=GG_JU=g$uZ2m~BIdK9^flDxcpK?9>ki9EII>br)*o40PsK+Ly! z96B;KKfl{*3)_ABXEpKK)?_pZ`Vj3;h;WP;C#uPi17p{%;b7brFG|Iw*) zlODYxBj-f-Gr4v{*p82nC%Epc@BKXZT2nLADoou;BF!6{sZpAG|HW~rS-D&5U8xG< zjTlZPYwKL6jfIRKKXf9wY-&c>3d1F62HX$!;9?ObovGs?lcYiQ@t}*YO}7h|IxJ>( zwL3Vh!)VgPLz%Jii5jK$$fSs`OCId+vD?ka7Z|tK=nv{GL%z__`FNa|D!sp7J(Axw zoZ1plVh1DSm~d{p)Z;{#j6&aNJ2-4D>n9{8lBbRmyKlaz@uQeL>#%A;W+t#R$Cmc| zHPl!d=_s=m+LwhyB$V8?G7wSr^L^tTH||6^PFzGH*19-R5;M~-EugS6(YUrb8esbU z!|m5TK2}qH$xdrCDTd8aGo5LnNU2`v=kj)4kGcQij=}c}0Yyt>r%%U7MRFTgdlAyJ zux!+=7(RItLn9UOv|(g%XM7}ri)2rl-KgbW5RGJ;hQolue0_Ap88lSPjJi6Jgrp?r zIjdtu|dn2y!GLtCApI&&tL!@N&f z0rp2lszzy{+FbjgBgdywlA^5p__y4Ij7>wx%07hdQ7dxZmMPWxHeC_K>CQ=X1C) zm$xr1!HE+sFNpc49#4isaUXDAET?kbpX)0~>*QQ1aadenm41K+^X+-+e92hLyJ((T zCgtpV8L9<8gf9UKiek8Y`5tVHj^xL2>f80<47XUL?CaiMaT1Qjh+y**KK zAT<(pRr|F7-XHIq(Je90V&B*)eRwh_ur|{v{)UW6Hc2XSZkx-Y-^v?8jc?(S&CWd5 zdCsw?PNM4BGo+MS&#Yr4XaFLKjn)P@ly(nXyLOG;dEE@cH-g$4qTp#1K#ayHKHLoL zBX++6-5{ix3sbvx%=f8b(Hd#rJiWb3HPpNbDG~?Eoq2X`2F88XGm@deYq~5wBD~1@ z1s&a5V5$_rTicPTfzypnzUcaV@w<##nj({A#NEdm{WbwRnnsvL^4mHuAu~3pUxcjk z^Aezha5h7K9bMgNUz6nIuQRa&I}sZotAc&RaX8W zD9|p)!dzZfwhDoTn;ALlE7Yy z5KAi_z!c&$l$04;`p*RQg(v9+gKFO)18Kn8yKb$@b3%bC- z!J!D{ND-C`#`CSNuI2f$Q|MxKcy2F3iir?&W+tXifPLxorFOH5Nli3n4WRW4 zJLKkYSm|cIjR>fP7R7UmZW}y+97^;{yE8S?OuI8x6xMgQRsb%Bsukk&Sf>|C7bmK) zIXSwusg5fX9BGTgpI%V)N@wQUv&KUqyLtO|YaFfz&~B3htdFIDH2>E1tV>FQP~M2B zc#~mu6p*_qitF<`IBGB8%C&ch@>&1@Y{;pIifc z|Kxyrnu^bz(K&_@{V7RfjBkBS; zUp`5S^4PIsDr>5}#Ws_$*{@g6SGsKFCQ5{TJg@omQ>xMc6WJSU8ymZ|83o`M2!Mtn z#ENMIQjw~L&HViN^Eq;Ila+Dy?*@6>8;e=N3`*8NvSWY#wB*|yexX?Y?LZDeA$bOV z3q_+u0n^dBxvWfD6%j|i)&nq1hVqD8V}lHs(9t1JDd46Kxl%z>GYYD0y!#O9BoUFK zh{!9z%Mdd$0H7|ao}D(BECob3vs6osH5ohs@V`nM5T!0wMTCKYp}=_q4RA(fXMMgM zo&`N!n=gssm>DR=0!lLuwWC#ojXpJHWZqxY8tXckAbuXxX}7&<3`qYbAD;$Hd4}Tf z>eABVr%#KnUcKs8;k=Hy zQBE^A16+hST%XL6!44r zp~|7mtcqjDkJI12{TOf$QhaiAb8TVbBkx-M@TNUi*ivF=iX`j|H7tj`^rJ_f3l%$P zjY_wH%vw=cOm?7_JHA0v*QURUg)Nf%Vkf;FM9V_HV-fOFN9#9nm z2-WW%63R53K3v5Q$Msk4mD)^7K^dz`ypmH^rmp3(A4`rZr;(i|R6u`0F`+GZ-PM65Y$hgOhw_qmOwLc$~jN(;+ z)S=it3TTN&GJL8hN0-V$ijJNhp**(hb1HyZRAJK#ly#YNU=*@6`Pk9@%2rlZFvHj_ zrx3uAa;5h3w!>A2s_g)Y8F!_t^c9+85VipX3%6@$>rBVQr0@q41B~n0o4`jiL)Na` z8CWz^)B>8czujFq)4)=>tNrQ~le~h0F;Ka_3fEGY!}v4TgpxJOoj6i<_IDP$0UHh2 zXILTSGZ}a#&NlZWAoVFf&OsRylhlF2VwfKwHhtyJ*eJJcV+4oMtgTPA3IgeJ5cy!* zUz8KO44JPzN!sCC{FSH}VAwVj-&-Ly5e}%o^7mi=Afu+NoD8R%&UR(MTHhJFaJBb(hZ_q!yFyAz%JTa9{@07O(JsrPf;Ojme)t8vetoi9w@X{i zAWy}8yFJ>h5IqY?hTXjHPN`Ig(&NXm1*Y95a6P9H&-3TbpEoRO0ya5=4DYk&DK0vc zX)`V4VP^=poL;#QKr2SOn@jeD`3E;?X@yB>BwB_J_8mg!0gSkrhrMfn_)%~bycYB%=3&yW;5cnX3{BU;f5Q|1hd{UH?7;OAPQCC4P zg2(dm=kSgJWmgx3I3NgM*d45J-B}$AlHUI@Mz$^kVn*fOyoGA^lV_4O?`vvmK%&sJ zA3_6HuHGXY_2m!dETN99*U6cJ-P;bZY;`&*x=(L83t0jfH^;^9DS+~C7q7}tIt)4{ zNzSNLxD;!;t|?%eNMtBMstCWW>ow?p;970x={|YFkO3l zauN3NFS@n<_5+F*7P{?6FStHBdtk6i^D3jJo2#vIe~pE2 z6Q3_i8NNx%N2*EbV#Vy)c}IYngaJyNm`0AsfFpo` zvcCbOAnb;fh@XshA51W~zdQVb*erMME6_{RQgl-5tuNfqn3t>d?4zR-Av%$Ku3BUn zE9f5$RlR(zOU-NMR+t2^U=lp?2pUF42|nlbJMzMciXobgVd8Tl&@2 zo=9d3U2{4lT{~Cn;kUWDnG9IoYgVP~X%4e`(XgyJXeVvyDtW8G9x1}V&_ez{bm)-1 zb3!<6K_UM8D_5?ZJatMA@Y(0;YEsZ|T3TcfRH4VZmwnW4b@8@7{NJ~RhW7D>&|nP4 z^y$^B4)_JIX8~eb_FP8~;qr}x_9xx;WS95@EtBfA4AZ>)6(>1FkThZc9L5CBd-+ zkTfntQFd!;*C{M)@&r~^R-Qe3wgZX-$OvMfIs&b8aLTqv(p@;eYl;0_&&bc_%+yp_ zF|pT(;+Z!4%Fk~+o-pb5cH%uqOCUYX1e)^zM{R3s>&Q^!Oq;#H`}9^U8LV9s0^zIA z*MrcMC}}`Z7z$8f+Mj3PR{l{zS=qpW2N|uj1?=N%qX1{t7KgOToi`BF)!-@Oa01du zrkz;K-XJ7eLQmDGj?XH)dwZR^`r?9>!HCW<&B3fyp{`M4o3pKMu((BwlK%2Rwd%M8 zP0(2q62eX>PDBXdO~9!rC?wnv54n&U2UK=>X(^{+{i17RW4o5XLR-QcgQkcSSPX>1 zne^qSh#yDp-JPZ7Ff%zhHb%$7(mb;absdn>#2BIXY)kBmSc6alENKb{;qQQ)BqlI4Ypd%LUc(gPnekUcuxo|M77I5}xJ#azxOn32SvA}JyQ zz$b4NQ8eJOm<8~Cc$(bi2xz6F?#K^L^%z!>3u*B|Z75L7w*jQ!^$llI?hK_Nrq9X%qXl&|y&%Yht0T9Sg8i z-wswDQ=(VOd!vKq`gKH1all0nD|)_sc_Ca*d$K7K-KlvEhbywut%ySs zTsqt}tpUcbFyy)si7VIwAg>r|4rEosE#1%Sic*wgNeR^AG?-eTy&fzyZfB%TMvQ;} zDuxy)Ga{miTEPb74#gox_FrI{8IKU2@KSYibF+*`U0t1J1MN$p2O0HxxeedbglO+s zZ_pONK)dse+s7s+C%fh?*L>1ZclkRsi!49QPw-_cGk$As#+LB@iftkiz~#v7)Be^ZNVN^2XFbG06ULU9L8ehF>=jAY_d=-gT?vq!2!bo0E`3xh z+<~<1^$qXXvEKcc20zzy9b9EiIWX+NRe{qic^)0k^(}j(W2XswKYqA zS6b90udF=P;g*NzKq-fbh)4&dx~zY6xDB#P2D9_twjI_i?_C1+>D~p{iA1`#xugz} z=?UrS-&>BIBze>;1e6p6xBByEJcPYf9riL-U*exQ^nmGJvYl9wOYREX1vmO>aL;l;hy+qnkOrTa3t+1irCfiuJ) zWFYyu;w7s8`B$Bsl61ZaZOfzAaIW)x>_p@-;mVyCZ`DnWk-mHP4&A!|MS+4|OI`i@ zC*78+XDH90)W3QGj?A;-tkCGp^~IqqL`erA3^=01X)P5XeqvjjY*?cK{0k%%(SJdj zKsY@Riw=k+L{`hm$(g&15|EXZ)v;5#Hv5Su=-vE;D2(!0KTQV*2y-=iGPsXJ=r2=2 zst(TcYn+u^evLC=8g!i3a)|{1JPT4ymcPphSW?1*R z9UTrw+oQK`0BiiE`S6ve1Nx3tnI5pSI|}Pv6bsp^K_^FfkK789n_6KhFlDcAE4t<1=QKc$44<#0AW+7kiCZZ6YTUI zW;?*@g5!(D zlT=uvq}KWVN_T-jj{@M|i!3-a9>_uIBQ+ko8NJP$vE>iUA*B3H6u|1SVlo6=qd^w{ zHP;@;AGarxyv8fEkrEnuK62{H`oe&zQA#*SlPRE!G7r=+Ym{6dzZ!_aG~A*@CQVaR z)CY>8NwPm=t>WEwRZvyEsoi>HD1o5cV_I9MJoDbgOijs{zAV?`;phtxvXH7`Z+<6q z^w_ahAF9tfIY1>EWhlYNvF}|e=r!vCyhzZQW7^G3bPl;eEto@ucs0Noifk!>^H|({ znv|H>XTIa>bp=2~MMVV^31_YwQ5`>$e0jry;sJYbx4tkNDCv~h0w4j1qV5I`*7I_8 zWssKZ8Yvz=tWm^y6F*H~+8Z~F!9HN_WTIfFa^+c8k^1=sWp4EpTw!@`%hb6V#?6R?_Ykwm6El#h*jlv`aKyu%TS z2q3)M4O;sjK~M}elC!kTPLzr?*^~?iMsMkX1V}NkGTq-Q8YqKsI&*s_9<_@M_)a!H z`#FXetV4Z1zJC3hAQ-zLN%DZnZDy`F&+8kf87vKtef%}PI`Ah5-MeSIv$7C~xxhJt zuBojRhB)WA!%)7um@~P)A9)la6-^JUD3nEuLtWa%r3{`U6)Zrma>>1&jczb%%!{Jn z8aUU8x2>nHJA`R>PTe-pQ*4^~xxP-*@uTvAxv_CdEH|P9VpYY-E9W4yfUpuY;-p$= zmU2@r@q#Kz*DKh^l9AjBP}5n}yWw#_0(wA1gsi_3pj7_-Ofof_7{|p$e%tV+%oj1C=Vbp%4)@i1}7e$C3#^5+Z0mJp_UaR%M%@ zGf(I1NsP-7kHbQGx0bsG;1}zubxxBG5s=wIV2Lzarp)PFI|4!Sq{lGY)!^xF&jw;7 z_2wB;Xnr0KktQ(6tMR=U2Z;rDU0$owO#`t<2C$`xyeZ}O^WnL9d4!!yb=LyuZry50 z#B~D5EMKajfRcAUmh9jLAK!o_B{A<$kL}fIK|`lk;3%0wM&w9gpr;o@9sv@cU|~7P zf#IAMQH{@6X`@|qf;h{OJ|zqQvui%`$6f5QhVijuo8gxAYP8Wvk5Nw@ik-#oTd{(&DOu!NJ;tgZw?BuOXqW z6fhC`7<-Pe2XktmdTMzV%i^$uPS801<%0wb0>*(Qbs#PyR8cwsfgzf;K~Rm!xvb+g z48FR~W?<*|LkelU`e2;1Iki-I^-G;uS^{hHI3+|@g;Z4lIBbl&S}tczzjx&^1e#y+HZ+)Dfs-;eS{iyBaZj` z_vZ%OcIG)Ux}kh;3^+tDly7Lx50C-gYPW!KtDvk*m)FY(<>43}996OtI>1RFJ$qKCk|h_~Hu8Y~zHHda<%wEC6Ut~_x6Ocw{s?aQyMM1%3L3VAFUkQ)%CkO6@#l{|xRR{d{; zwJC5jEkwIP^t>=OLlcO2U$nA zyZP1Y*KOa!*etl0kX9KeMKjqd^8@uz$qKB;#RAZyA0NFjW2LwY$#Phx7Fq{xj~c-8 z#{;eA9?$a|ylaU?779qRx4#-q$7eEDR@QvI`m^5d3rC1~pJ0_{Dz(tbB@JaC8VwNj z24sOrk4+zhDMLY_>MbN?P@IAm30+!bp83psvhO3wUl?lzsZFZ<^lfsl{@Wd5Z4l54 zk4INb+SCZydm({;1R=C7HhLQJ;Mka6X~zIOkXjT9iE46gTROaILPphm5kv(REb7l&#i0WK%r}DWrplSgGVN+11d(GYBD-pdE ze0>D_Gw1L@$gs&*Ko3vGMlB@mf+wF2V~N#;HgxSSxN6|SW~1Dir(`c@=Dx23=Vi4+ zPI~%_4++s0uk*m>(#lF+Nd#Z_1NLp6(UX;kYTbq;1}NjjoyBui&yFB=KV;=vPhyx( z-u7ol@DLf3dllc4X?${W7W_23lzLte_bigjN*CT7SDWHYg_I25ldgZ?6jj6|*JwYs``I-K9TX9J#){X)O7-3Qy%yDb!4j?3|}C zC^R$)Y59WOk5%WmapTdD%Zi^k)e-2*X#?WYnQtry=@-X)4%m}8cxoI&3t&nSQgX{f z`o_wmM?QOcMI|aumPLZa1s3t3buXkA99p{24?5dgAga~?OwcnkH>ECmWFUqh(#L~H z?yxPatd5*Ec@}* zI(i}Z{=h3(3}bLiQj?Qqpvi!e<6@V?kZV$EDt#x+Tc19-oa~VQy(K>?%E-ik)d%fj z#eMqFJQWA&vsRyG4Ad1Ru&>c65>OP8bQ#J&2yK;Xkie17JJ4pb*88$Li?6(kib98) zR{+F>2?sM;X`U6Sv4B?0Huia8{h{~dsW>MV=~IHOQnI?m1m6ciu@hWadFVfY?gN~i z%d+34yO#GLa@!%J5D-5*gzSt5U`-+CcdTYeIG|Q!Rqm^1l`X&KuoyH8 zO`<4N1o0n~JEVaLdK;UdI|dfrY3b|v;IOdioLU6{`_TRs92oc!R0=RSp&WNlLNdHCT>qfV>BOxO{l`E*7o{D|nNJW+DU~xZ43sk7V;w^a^05j%;l~#9>{v zAcmGbsCi$nh%1wxJ4Xj?kbF+7$z4HX$l3vbMCX(#!uVO)*b20(kAsK%_{NPJCP;^f z?zjPV9vN%5!OLkG6n@38XA%x*9K>0Wg)ZzI3?JZtuskaKx9BbBpg8I z2a4uyH_7)`uTJnmsxiS8&Np}20L@3T{>Za+fg9Ja8-X!`eBkbhgAPwuz!QB%R&nC~ z@7R8KhyctR)Y>fti>UF#?dp~b;)d8fu#IMhDk~9}9Qr9ug2CKw|MB%rfzz5vt-5lU zLM-`g-i z;}3!uk@hO^-VB=}T0v+X(9tSH52ykhc=LN`$m*b5*QD!{l9#0)?i`b}kFcUNOsz}6iobx?w_Kq$ro3zz}12!`b?XZVF3 z)A$F~QD|mPT?Q@YMNtSuN1Q#txlRU{@erx6slx)0L4{Rdo;ORw^ho;L5kc z{)OH_JMds;AVYIIEJT16=pa7Hjr22jmR^kj5ZAgwFIPO zMzT|s$XiOljVD*u))b)C7H}E^V(Kg{Edj&W>8;F4Pe+=WEB4mapkqJ_MlUBNMMQ}_ zA66flZ!a2Xt1iBE5uw;q4F7!I?INN(VJDZWLPM5oO_fq{Cne zILHk+T#n#ydt2Kb(=JBHtA3GiA5`eqKz@%m@c8ZVqCp4*zsn09^iX^5I4r2MoAtbn zL|)Vat!X9=8Uw&IPeR(s?07!L#UXB}-Bio7NaP(ZM2$K{MAYSbk?kBM<@{M3WFX3N z@QGn_vGCx*8L&bEEU?H(iih6RF=;7S0-xt_b1B(^*89%nCtElL% z-$gdBNaU}6fMr8EkE8|m^1mBwY;n`T3c$Xe&A=1*!+a^9Z{Epbr`S z_~OG24JHNq`$%svbi6~)E<4g*P7_q$G8q~YG6QXv+TO5qRW~olow_TUrCCnU{VqH_ z6(liD2sy0!I%q1LkjoBc>oeK=I`QD5lu z2h|ZYAP-99uitpUBqR6K(eWk+N0$txMfq9?Bpd>ZhS#JFw)?w|f_C7S`A7bB;vEph z`1$!2L#4&V+X07}=Q2UH-JaDtFi9qgJ46ZBgXKU&jM8|0v*^kGl!(g%YK64d%q&C4 z{G9x+$9oq3S>yyGC|8j2%}V&l-h7pb1OwzhKLv;|lM9HOP`85 zeRfai6zbGpSzeyYl@yv;+t@Hnf*ArOQ#{=AV<7T?EERy{J{^ZL2P7o%6;2zh=YyrKV zDxQo&u9u!UinNzQrY1D61sUsobaXS(pOJ|oI(?BpFda~hR8HdK7Z-`c zXBxiq7vk}UUJ#SdFY!6jSOf&LjE#-^pfd;2U;b<{1grhoVqk*y&8@&wB|(VfT>CS$ zt1K)m(5%=0^PyxDhzaLlt>64g$4Mh=x>Y`){;ZC8QxbEKM?G#^rY57+O@7ZroJ zjc`Cj2A9KL;snCfAT_Yio^#ldn0fs=IIlV42 zF~e;V&^E7Ndt|upfuP`8k8S|c6%G{#cqfx)nKBM*k@Q!^;Q`gZ1Dd@Pz@LW3jNW{N zLLrz@wbHE|=6uQw?)|I60i;5@Dv`Em0Jntze?s&&=nfIbgOOFmJ#9bcVF>R$!MeyQDw6%GcmcKC|4-l9{nu9^AyQz<{(7ew zrVjbP|H47YT>tZ#|8x-I3Hbux2IR+|GymxzQds||Gymz}Ur6oG+it(V{@1}jqy8@* z+&x&IeT&@vUdur@I=lKP28BvQo?i|b^{?L~kf;04-##EOZr?%{_CJ4ng3RWxW50f@ zLZ;dy1;+oMzx}%4uVep Date: Tue, 12 Mar 2019 17:25:38 -0400 Subject: [PATCH 36/72] Upgrade sshpk in yarn.lock (#333) --- frontend/yarn.lock | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/frontend/yarn.lock b/frontend/yarn.lock index 5c2f5ad3..f64632e5 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -10355,9 +10355,10 @@ safe-regex@^1.1.0: dependencies: ret "~0.1.10" -"safer-buffer@>= 2.1.2 < 3": +"safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== sass-graph@^2.2.4: version "2.2.4" @@ -10791,17 +10792,18 @@ sprintf-js@~1.0.2: resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" sshpk@^1.7.0: - version "1.13.1" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.13.1.tgz#512df6da6287144316dc4c18fe1cf1d940739be3" + version "1.16.1" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" + integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== dependencies: asn1 "~0.2.3" assert-plus "^1.0.0" - dashdash "^1.12.0" - getpass "^0.1.1" - optionalDependencies: bcrypt-pbkdf "^1.0.0" + dashdash "^1.12.0" ecc-jsbn "~0.1.1" + getpass "^0.1.1" jsbn "~0.1.0" + safer-buffer "^2.0.2" tweetnacl "~0.14.0" ssri@^5.2.4: From e160380daa64886fb4de4948dd13317f6e94fc57 Mon Sep 17 00:00:00 2001 From: William O'Beirne Date: Tue, 12 Mar 2019 17:35:15 -0400 Subject: [PATCH 37/72] Prevent showing contribution instructions for canceled / expired proposals (#334) --- backend/grant/proposal/models.py | 13 +++---- backend/grant/proposal/views.py | 14 ++++---- .../Profile/ProfileContribution.tsx | 36 ++++++++++--------- 3 files changed, 34 insertions(+), 29 deletions(-) diff --git a/backend/grant/proposal/models.py b/backend/grant/proposal/models.py index 22a44834..148f68bb 100644 --- a/backend/grant/proposal/models.py +++ b/backend/grant/proposal/models.py @@ -517,12 +517,13 @@ class Proposal(db.Model): 'support_url': make_url('/contact'), }) for c in self.contributions: - send_email(c.user.email_address, 'contribution_proposal_canceled', { - 'contribution': c, - 'proposal': self, - 'refund_address': c.user.settings.refund_address, - 'account_settings_url': make_url('/profile/settings?tab=account') - }) + if c.user: + send_email(c.user.email_address, 'contribution_proposal_canceled', { + 'contribution': c, + 'proposal': self, + 'refund_address': c.user.settings.refund_address, + 'account_settings_url': make_url('/profile/settings?tab=account') + }) @hybrid_property def contributed(self): diff --git a/backend/grant/proposal/views.py b/backend/grant/proposal/views.py index de00e6e3..89839345 100644 --- a/backend/grant/proposal/views.py +++ b/backend/grant/proposal/views.py @@ -461,15 +461,15 @@ def get_proposal_contributions(proposal_id): @blueprint.route("//contributions/", methods=["GET"]) def get_proposal_contribution(proposal_id, contribution_id): proposal = Proposal.query.filter_by(id=proposal_id).first() - if proposal: - contribution = ProposalContribution.query.filter_by(id=contribution_id).first() - if contribution: - return proposal_contribution_schema.dump(contribution) - else: - return {"message": "No contribution matching id"} - else: + if not proposal: return {"message": "No proposal matching id"}, 404 + contribution = ProposalContribution.query.filter_by(id=contribution_id).first() + if not contribution: + return {"message": "No contribution matching id"}, 404 + + return proposal_contribution_schema.dump(contribution) + @blueprint.route("//contributions", methods=["POST"]) # TODO add gaurd (minimum, maximum) diff --git a/frontend/client/components/Profile/ProfileContribution.tsx b/frontend/client/components/Profile/ProfileContribution.tsx index 1fb97adc..5863e792 100644 --- a/frontend/client/components/Profile/ProfileContribution.tsx +++ b/frontend/client/components/Profile/ProfileContribution.tsx @@ -8,6 +8,7 @@ import { formatTxExplorerUrl } from 'utils/formatters'; import { deleteContribution } from 'modules/users/actions'; import { UserContribution } from 'types'; import './ProfileContribution.less'; +import { PROPOSAL_STAGE } from 'api/constants'; interface OwnProps { userId: number; @@ -26,7 +27,10 @@ class ProfileContribution extends React.Component { const { contribution } = this.props; const { proposal } = contribution; const isConfirmed = contribution.status === 'CONFIRMED'; - const isExpired = !isConfirmed && contribution.dateCreated < Date.now() / 1000 - ONE_DAY; + const isExpired = + (!isConfirmed && contribution.dateCreated < Date.now() / 1000 - ONE_DAY) || + (proposal.stage === PROPOSAL_STAGE.CANCELED || + proposal.stage === PROPOSAL_STAGE.FAILED); let tag; let actions: React.ReactNode; @@ -43,15 +47,14 @@ class ProfileContribution extends React.Component { } else if (isExpired) { tag = Expired; // TODO: Link to support - actions = <> - - Delete - - Contact support - ; + actions = ( + <> + + Delete + + Contact support + + ); } else { tag = Pending; actions = ( @@ -76,9 +79,7 @@ class ProfileContribution extends React.Component {
+
-
- {actions} -
+
{actions}
); @@ -89,6 +90,9 @@ class ProfileContribution extends React.Component { }; } -export default connect<{}, DispatchProps, OwnProps, {}>(undefined, { - deleteContribution, -})(ProfileContribution); +export default connect<{}, DispatchProps, OwnProps, {}>( + undefined, + { + deleteContribution, + }, +)(ProfileContribution); From 21217c73b6c891c8d3ee11400b3deb605a08bc4f Mon Sep 17 00:00:00 2001 From: Daniel Ternyak Date: Tue, 12 Mar 2019 16:36:17 -0500 Subject: [PATCH 38/72] Create DISCLOSURE.md (#335) --- DISCLOSURE.md | 119 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 119 insertions(+) create mode 100644 DISCLOSURE.md diff --git a/DISCLOSURE.md b/DISCLOSURE.md new file mode 100644 index 00000000..29e6d296 --- /dev/null +++ b/DISCLOSURE.md @@ -0,0 +1,119 @@ +# Responsible Disclosure Policy + +We greatly appreciate any and all disclosures of bugs and vulnerabilities that are done in a responsible manner. We will engage responsible disclosures according to this policy and put forth our best effort to fix disclosed vulnerabilities as well as reaching out to numerous node operators to deploy fixes in a timely manner. + +## Responsible Disclosure Guidelines + +Non-critical bugs can be repoted by creating an issue on [GitHub](https://github.com/grant-project/zcash-grant-system). Do not disclose critical bug or vulnerability on public forums, message boards, mailing lists, etc. prior to responsibly disclosing to the Zcash Foundation / Grant.io teams and giving sufficient time for the issue to be fixed and deployed. + +## Reporting a Bug or Vulnerability + +When reporting a bug or vulnerability, please provide the following to contact@grant.io and CC contact@zfnd.org. +* A short summary of the potential impact of the issue (if known). +* Details explaining how to reproduce the issue or how an exploit may be formed. +* Your name (optional). If provided, we will provide credit for disclosure. Otherwise, you will be treated anonymously and your privacy will be respected. +* Your email or other means of contacting you. +* A PGP key/fingerprint for us to provide encrypted responses to your disclosure. If this is not provided, we cannot guarantee that you will receive a response prior to a fix being made and deployed. + +## Encrypting the Disclosure + +We highly encourage all disclosures to be encrypted to prevent interception and exploitation by third-parties prior to a fix being developed and deployed. Please encrypt using the PGP public key with fingerprint: `46CD57E95AF395A1499C18A3F01C867EEB456C7A` + +It may be obtained via: +``` +gpg --recv-keys 46CD57E95AF395A1499C18A3F01C867EEB456C7A +``` + +Alternatively, it may be optained by copying the following into a file, and imported it via: + +``` +gpg --import +``` + +Signing example: + +``` +gpg --encrypt --sign --armor -r contact@grant.io +``` + +``` +-----BEGIN PGP PUBLIC KEY BLOCK----- +Version: Keybase OpenPGP v2.1.0 +Comment: https://keybase.io/crypto + +xsFNBFyIGnABEAC864WlC/UVmKfaDPWgCt5EppPV8H5KXnKRy/iwXmDPetWpWiYC +UmcvFuXc+cD+RLuqwmei5K/74QgmGxCNiuWsa22cXF5xkQDHwSSAhw6DisoT//OK +ftcn9HBB88nIzzGuRedv1eyGV7fc4syACkSZS1PgplLC5R3mvKIGUXz9mbSdL2HM +7ao4mTNFo6wgPkebBX4w1CHQgyU327HJAVRt59VMrI85ahoU9b2K9UjVfbFartQs +cU276EmOXC3Sd+3JLyqfOXsK5W+bInvC6hnoXlQpPA9Zv5RIshHHTSW31M9w6inI +SkJa35emvt/UIVPEAbm3UtkYzD6YstOBQnGqUBQzRSU14fvPuuX4FQXUORSEcYv5 +KdqNzj0BCD5BNr992L8+FRcQnHm1+d8LgCYHzg2lpaQ5bgXYi0lL5HitlD9+Je9k +btqKYF0qESQRDMLYyTYV06Ka7Uu0Yd1V/7+URc4OkkgMRBBAw/RVBWzgrz1Vu+T7 +EZynhATn4z7StXf3RLikuShqL9y0nFgIzJuwInFdwlngX0WNetDIOvi8sif28N8K +C5Fq9+Js1hKii4YAxz+kkAXFjYvkebr5BhEJsWfek2Y5Bq4a1ZYJHeqxl9EwUYpF +nKy6sLWIfxUckfGWb26YSOONhFkxzDbPt+JTFSgS0Plt0FTI7cqCbXJlcQARAQAB +zRtHcmFudC5pbyA8Y29udGFjdEBncmFudC5pbz7CwXQEEwEKAB4FAlyIGnACGwMD +CwkHAxUKCAIeAQIXgAMWAgECGQEACgkQ8ByGfutFbHr9uRAAuIF/L9tve5TNjqBC +X1Vku3+VgN1sLQu8JWzTDmwmAp0UHd9wXV7Yw6NR6jny1Os4SEibBA1LgWU/f56W +m3y39xzZGFnbD81BucGh676PB7JNnfSscLhggrZOtAP+sEFAlg+0vJM46l/TnXtD +6+tc7/J+skHrcwKUBNamZh6UkE+1E/Qi7EHCemhJlW9QAN8CUPKhM05OSb8wypBF +HY50QROA+/FpvUUHY4iumJmZujUWQ2os+NM+KKvFQtkQp06vsk5jCpqEGC+YTVr3 +GArkIEQtJgsiM+h1KsYxBzQfmBVabzire+Xi1csskzY/vuQbqk4FaeaHjExuRcGk +vyblBdAvSIgjW3PNrZauWrlu92Rxmlpb2+gtPcQ+hxKxaGWKghrOTR3hx6maV993 +T5m00OGRAk/7yc+yZPUCBuZt6qDtcBWOZfkK5KJb/gGSiH/Xyt4v115qmaTHnZya +lxzZrkAFBaa4qTp5xmu2bK+KQ9kj5PS9X8l8aGCICMDZgjQdCC9APUNbqTuDqUqo +SkBPzfheBCD+5dlZ+M4ToZvG3sXd7QF6OKsb717sz9SFAfG5gDMtO7E66kspclh6 +KAflOyKp/J4irJzmV+bJ2L/nRbCgaGxAL8mA812QW9VICG1LH+2FmAolenwXrFNj +g9dUFE6qBNRPKuMqze/6/qAf67bOwE0EXIgacAEIANXkJ2EM0HepjvrCI3m/VIEY +PSejIDgU90l3miNiziJE8tfUrhjXIa5w7xp9bNyzLQW3W10oP0ZEw7nwWweuccyg +jjVj2GlgdDjZ/GngxbKxSqyeKeomy6hYnX91lEY6FIhoceSdi6YM6XUc/8vBv0l9 +ErRXm0g+iFILXSsVLf8HlB7iWr53FG54MHh8+VD4Q+kykX/eyEdIClwvaIrlTc74 +xJmQwAMv9RZcjcAaMjd8xTHd4qHvo/bf82DRXdnwfdMUwNF0DGL05TNOohACPddx +qUMq6mn4hhfpp7QN4z1IwkshyNyWZHRxXckNIqW2ACCSmCj38dkEquaVNrK4LksA +EQEAAcLChAQYAQoADwUCXIgacAUJDwmcAAIbDAEpCRDwHIZ+60VsesBdIAQZAQoA +BgUCXIgacAAKCRDDgnzS4GP2HYeUCADPtnAf+Q2y6bMSInS+J7kgnRYANYQptVPC +lAC2PrSrJFtcjaF0LYUvdoXZHoNRx1EqaAVpfT/lBHsMxIo+jBR555yPIPZdVXcL +W0WfvqvQ37rznHPEsGTMwHnVfr4gPkr1SvdGHhbTvmJTPeYAqG6+7I1QBbvRxnnD +iE+4HXPu+l1uCa4aA196S7QrBKAIQiLEIKlSefGNcZrITPnqybO8FCVfbx4sJKac +0zYFxuJ7ZNAMUOjCwrQD08CCX3Po6SWtmrH6LfaQU5DIO/9CX+9jp4b7FdRrbl/K +otTQVIIw3yPaVeMUczOhdtvVrBvtubMhohwdc3LJB8RJ/VLZti0GjzIQAJXjmfrV +TDk84NBtigorHO0WU1iUdHpw3J2LUX3SczkywQZ3Q/p5j3C2J813FoBJ433fh+ED +s/YS/FO/lJSYaXdBAE8Er6EUvQyJIabKIpPrZtbshRV+An9Psq/M5sP2alLX38i0 +UqocUbdUGI6jOrmjNDKf5G5mCvE27jJVLBytKOHN9EJGr5WT0g8VuL5JCLrRHJ5D +BVLfocH6q9OfW3cAh5ULH/ZHizTecZg93gDdpjGxY46SyYyTmdyzsKxIKx74vLw6 +6rsIh+Hv0zw7bjTBwFpLVy7poRn4gNpD90n9U1FAYDSwNzdZlEAAPUiIO1BCSaf6 +20Bt6pB4gg3cWXRFuQBjFYmlyHdZwns9iO2gbsA7iNrKHn7o+vnRbEgEiAyEg3cF +y5x4j4U34WcvPbmYPjUEiIzBt0VtnEtuBJa1GHcINICotgfMOM1W7fFQwXVK2kvF +K7/8BcXQ9KYvzPgDIijIdIgg1jwqrZcAbSF+q2ogsyfyowptQtOeigqYFdXehXwR +lgMX8DTjUQ5rcpGSHPDaEOvA68RtR9IWv4r3EIbKVLvoGHePr9L+3FmxcU5ZbG2s +kRLI94eOJvt9sHLq24+SDd0Nekd1MLT4FK1HWDwllAoxPES0qw0sO1P3QtYT0jE/ +7rzyL4QKUfaPhQzqmr3G+bp+3KkdMrqQkrnIzsBNBFyIGnABCADYrqV/3RaMwWsl +umkiv569p4TGwDGivbMbIp+OXSGDRygmIcSsK58HTLUK3GYntSspUinVk6mDGT1I +ndTX+GtOXDs3A6x6Z3zFEfKKjBYp0FshD6Ite3sTLUX7rEbMVlyB3qGakVR0PYFD +O7FXsGlMewvgJ82pna9sRGEB8ZwspSm3qVlUvgL3+Lj14+i5+pINrx8Oslcb3Tqq +XqDHv7/6scThVHDVIjBWNp1V9G+8lGYuromosMEtfjjctvexCdcuM5ecWkfl4lhJ +Y7Y+2mb8ZPKPRBxJm9jU9ROCJYiOAhDB51QMfukc+sOtAWy+M7jmdK4Y5StqDkH2 +zbNCYQvdABEBAAHCwoQEGAEKAA8FAlyIGnAFCQ8JnAACGyIBKQkQ8ByGfutFbHrA +XSAEGQEKAAYFAlyIGnAACgkQRzZ4O9hHqSAKQwgAlspfIcY1jQEq6KA1NPEBWHKR +hIiB7RPI+dcZ9YBKVxWXSMj13XSWor+eWL1Hkimks0Khf+TjgAzP9x032ecbeZr3 +xinFAE4FagQkim52z9lRAa62tqOETKBsvmV91FszphZj8pcJazfxB7U7Sssmg+LY +TVLe0qLmJ3RZbS7SuknJ+kRz5gs9NrFWLszhfWdKM7soznkOg0ld6Ut2iuI63ZzM +9UJns4as7ZXA8sCGbcMGmekyf2YdRhTK5+UuC97YjF9NmX5RojxRfQpvffAA2j5r ++/f4Xc3QdJxXhqEYJKea4+3xslfT+rV8QeG+H22ooPC5OO3auq5p4KZIUZO9p95v +EACZNXsgPv3OHSftqmJ1d5Dq76sbNeeQDQ24S/YKGyHI7KJlQcQumMBonK4gLiww +GBcnkdTrvhZHTRxURhrUgnPnlYuEDZpuH5BN8HUxFrNk+2AV04efco8uH8Jo+dR9 +RG9ymM+SUsL1u+09ve+dkUlcM+uUU5QC+HqNcj6XePeYNcXMKGaP+W1DNvWtdQjs +HLtPqCJ9/ZneTy51jmfq1+MEVIuWDePuzrSzgMr2hmZpMRJP8DrPqxdlGjy4ydAg +WOA1GngILfgAjhn+WvXYAGl/u4dMxGTm93Debp3qMiA/3U9Mp6ZtBqLqkRHsbT8P +ow4ZDHDO/4SGoKCJJyp91MM8bq1tRrZnpmtqN8D9rmvJPRbksmgnzYVif3rYyei+ +iyp8dN8llNrAlP/dOSTS+dVlN6tJXvqp/wbhghxQ88Gl0h5E+bAtBaact6A7ypg8 +3UEJD8vbZi/SlIrmBE4wRNkcmGhT7SBCbt04o36ZgX57P3KMZgnFv3g2AIWmhL8C +szKKmnciuRky8/Rp35UZygxNlSMfwFNz3TIPu0rTqrEZ/TqIzBI4Do82PCBb8uRu +YuE2wMSvdPhaQSs1siFICIBrCu/nH1AcgLO2R20vtWi3azx+zLq20l1mQXgUDvfB +Xy9U/6jQi/pDWSFTLF/tj9ctvfGJXs03lkTrKyp7xAu5MA== +=1KTe +-----END PGP PUBLIC KEY BLOCK----- +``` + + +##### Inspired by [this](https://github.com/Bitcoin-ABC/bitcoin-abc/blob/master/DISCLOSURE_POLICY.md) dislosure policy From 79db2b4001b8d84a20200ed273dad62a4fb7e723 Mon Sep 17 00:00:00 2001 From: William O'Beirne Date: Tue, 12 Mar 2019 18:20:11 -0400 Subject: [PATCH 39/72] Fix contribution confirmation on subsequent contribution (#336) --- .../components/ContributionModal/index.tsx | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/frontend/client/components/ContributionModal/index.tsx b/frontend/client/components/ContributionModal/index.tsx index 9a8a903f..0814ce0a 100644 --- a/frontend/client/components/ContributionModal/index.tsx +++ b/frontend/client/components/ContributionModal/index.tsx @@ -71,6 +71,7 @@ export default class ContributionModal extends React.Component { this.setState({ contribution: null, hasConfirmedAnonymous: false, + hasSent: false, error: null, }); } @@ -93,16 +94,17 @@ export default class ContributionModal extends React.Component { message="This contribution will not be attributed" description={ <> - Your contribution will show up - without attribution. Even if you're logged in, the contribution will not appear anywhere - on your account after you close this modal. + Your contribution will show up without attribution. Even if you're logged + in, the contribution will not appear anywhere on your account after you + close this modal.

- ZF Grants is unable to offer refunds for non-attributed contributions. If refunds for this campaign are issued, your contribution will be treated as a donation to - the Zcash Foundation. + ZF Grants is unable to offer refunds for non-attributed contributions. If + refunds for this campaign are issued, your contribution will be treated as a + donation to the Zcash Foundation.

- If you would like to have your contribution attached to an account and remain eligible for refunds, you can - close this modal, make sure you're logged in, and don't check the - "Contribute without attribution" checkbox. + If you would like to have your contribution attached to an account and + remain eligible for refunds, you can close this modal, make sure you're + logged in, and don't check the "Contribute without attribution" checkbox. } /> From c5979d5635628390211f093e9289a9d5efa343b7 Mon Sep 17 00:00:00 2001 From: Daniel Ternyak Date: Tue, 12 Mar 2019 18:04:32 -0500 Subject: [PATCH 40/72] Various Fixes (#340) * copy; ff * Update PaymentInfo.tsx * Update Final.tsx * Update index.tsx --- .../client/components/ContributionModal/PaymentInfo.tsx | 2 +- frontend/client/components/ContributionModal/index.tsx | 2 ++ frontend/client/components/CreateFlow/Final.tsx | 4 ++-- frontend/client/components/CreateFlow/index.tsx | 8 +++++--- frontend/client/components/Profile/ProfilePending.tsx | 4 ++-- 5 files changed, 12 insertions(+), 8 deletions(-) diff --git a/frontend/client/components/ContributionModal/PaymentInfo.tsx b/frontend/client/components/ContributionModal/PaymentInfo.tsx index b9d6ed0c..783741a4 100644 --- a/frontend/client/components/ContributionModal/PaymentInfo.tsx +++ b/frontend/client/components/ContributionModal/PaymentInfo.tsx @@ -113,7 +113,7 @@ export default class PaymentInfo extends React.Component { } else { text = ` Thank you for contributing! Just send using whichever method works best for - you, and we'll let you know once it's been confirmed. + you, and we'll let you know once it's been processed with 6 confirmations. `; } } diff --git a/frontend/client/components/ContributionModal/index.tsx b/frontend/client/components/ContributionModal/index.tsx index 0814ce0a..4203331a 100644 --- a/frontend/client/components/ContributionModal/index.tsx +++ b/frontend/client/components/ContributionModal/index.tsx @@ -105,6 +105,8 @@ export default class ContributionModal extends React.Component { If you would like to have your contribution attached to an account and remain eligible for refunds, you can close this modal, make sure you're logged in, and don't check the "Contribute without attribution" checkbox. +

+ NOTE: The Zcash Foundation is unable to accept donations of more than $5,000 USD worth of ZEC from anonymous users. } /> diff --git a/frontend/client/components/CreateFlow/Final.tsx b/frontend/client/components/CreateFlow/Final.tsx index 81d5eaa0..fe36a4cd 100644 --- a/frontend/client/components/CreateFlow/Final.tsx +++ b/frontend/client/components/CreateFlow/Final.tsx @@ -102,8 +102,8 @@ class CreateFinal extends React.Component { profile's funded tab.

- Once your payment has been sent and confirmed, you will receive an - email. Visit your{' '} + Once your payment has been sent and processed with 6 confirmations, you + will receive an email. Visit your{' '} profile's pending proposals tab {' '} diff --git a/frontend/client/components/CreateFlow/index.tsx b/frontend/client/components/CreateFlow/index.tsx index 0cf25241..db17670f 100644 --- a/frontend/client/components/CreateFlow/index.tsx +++ b/frontend/client/components/CreateFlow/index.tsx @@ -231,9 +231,11 @@ class CreateFlow extends React.Component { )} - + {process.env.NODE_ENV !== 'production' && ( + + )}

)} {isSavingDraft && ( diff --git a/frontend/client/components/Profile/ProfilePending.tsx b/frontend/client/components/Profile/ProfilePending.tsx index 4332ebfa..b27e94f3 100644 --- a/frontend/client/components/Profile/ProfilePending.tsx +++ b/frontend/client/components/Profile/ProfilePending.tsx @@ -143,8 +143,8 @@ class ProfilePending extends React.Component {

Please send the staking contribution of{' '} {stakeContribution && stakeContribution.amount} ZEC using the - instructions below. Once your payment has been sent and confirmed, you - will receive an email. + instructions below. Once your payment has been sent and received 6 + confirmations, you will receive an email.

} /> From d1e2545b49a88efccf44a93bee4c3835777a41c8 Mon Sep 17 00:00:00 2001 From: Aaron Date: Tue, 12 Mar 2019 20:12:07 -0500 Subject: [PATCH 41/72] be: EmailSender Thread for pushing emails sends off the response thread --- backend/grant/email/send.py | 64 +++++++++++++++++++++++--------- backend/grant/proposal/models.py | 20 ++++++---- backend/grant/proposal/views.py | 6 ++- backend/grant/task/jobs.py | 24 +++++++----- 4 files changed, 77 insertions(+), 37 deletions(-) diff --git a/backend/grant/email/send.py b/backend/grant/email/send.py index d56b79ff..ece870fb 100644 --- a/backend/grant/email/send.py +++ b/backend/grant/email/send.py @@ -1,4 +1,6 @@ import sendgrid +import time +from threading import Thread from flask import render_template, Markup, current_app from grant.settings import SENDGRID_API_KEY, SENDGRID_DEFAULT_FROM, UI from grant.utils.misc import make_url @@ -317,11 +319,10 @@ def generate_email(type, email_args, user=None): UI=UI, ) - template_args = { **default_template_args } + template_args = {**default_template_args} if user: template_args['unsubscribe_url'] = make_url('/email/unsubscribe?code={}'.format(user.email_verification.code)) - html = render_template( 'emails/template.html', args={ @@ -349,8 +350,14 @@ def generate_email(type, email_args, user=None): def send_email(to, type, email_args): + mail = make_envelope(to, type, email_args) + if mail: + sendgrid_send(mail) + + +def make_envelope(to, type, email_args): if current_app and current_app.config.get("TESTING"): - return + return None from grant.user.models import User user = User.get_by_email(to) @@ -360,24 +367,47 @@ def send_email(to, type, email_args): sub = info['subscription'] if user and not is_subscribed(user.settings.email_subscriptions, sub): print(f'Ignoring send_email to {to} of type {type} because user is unsubscribed.') - return + return None + email = generate_email(type, email_args, user) + + mail = Mail( + from_email=Email(SENDGRID_DEFAULT_FROM), + to_email=Email(to), + subject=email['info']['subject'], + ) + mail.add_content(Content('text/plain', email['text'])) + mail.add_content(Content('text/html', email['html'])) + + mail.___type = type + mail.___to = to + + return mail + + +def sendgrid_send(mail): try: - email = generate_email(type, email_args, user) sg = sendgrid.SendGridAPIClient(apikey=SENDGRID_API_KEY) - - mail = Mail( - from_email=Email(SENDGRID_DEFAULT_FROM), - to_email=Email(to), - subject=email['info']['subject'], - ) - mail.add_content(Content('text/plain', email['text'])) - mail.add_content(Content('text/html', email['html'])) - res = sg.client.mail.send.post(request_body=mail.get()) - print('Just sent an email to %s of type %s, response code: %s' % (to, type, res.status_code)) + print('Just sent an email to %s of type %s, response code: %s' % (mail.___to, mail.___type, res.status_code)) except HTTPError as e: - print('An HTTP error occured while sending an email to %s - %s: %s' % (to, e.__class__.__name__, e)) + print('An HTTP error occured while sending an email to %s - %s: %s' % (mail.___to, e.__class__.__name__, e)) print(e.body) except Exception as e: - print('An unknown error occured while sending an email to %s - %s: %s' % (to, e.__class__.__name__, e)) + print('An unknown error occured while sending an email to %s - %s: %s' % (mail.___to, e.__class__.__name__, e)) + + +class EmailSender(Thread): + def __init__(self): + Thread.__init__(self) + self.envelopes = [] + + def add(self, to, type, email_args): + env = make_envelope(to, type, email_args) + if env: + self.envelopes.append(env) + + def run(self): + # time.sleep(5) + for envelope in self.envelopes: + sendgrid_send(envelope) diff --git a/backend/grant/proposal/models.py b/backend/grant/proposal/models.py index de2d984c..936e3f28 100644 --- a/backend/grant/proposal/models.py +++ b/backend/grant/proposal/models.py @@ -6,7 +6,7 @@ from decimal import Decimal from marshmallow import post_dump from grant.comment.models import Comment -from grant.email.send import send_email +from grant.email.send import send_email, EmailSender from grant.extensions import ma, db from grant.utils.exceptions import ValidationException from grant.utils.misc import dt_to_unix, make_url, gen_random_id @@ -507,19 +507,23 @@ class Proposal(db.Model): self.stage = ProposalStage.CANCELED db.session.add(self) db.session.flush() + # Send emails to team & contributors + email_sender = EmailSender() for u in self.team: - send_email(u.email_address, 'proposal_canceled', { + email_sender.add(u.email_address, 'proposal_canceled', { 'proposal': self, 'support_url': make_url('/contact'), }) for c in self.contributions: - send_email(c.user.email_address, 'contribution_proposal_canceled', { - 'contribution': c, - 'proposal': self, - 'refund_address': c.user.settings.refund_address, - 'account_settings_url': make_url('/profile/settings?tab=account') - }) + if c.user: + email_sender.add(c.user.email_address, 'contribution_proposal_canceled', { + 'contribution': c, + 'proposal': self, + 'refund_address': c.user.settings.refund_address, + 'account_settings_url': make_url('/profile/settings?tab=account') + }) + email_sender.start() @hybrid_property def contributed(self): diff --git a/backend/grant/proposal/views.py b/backend/grant/proposal/views.py index de00e6e3..9421063f 100644 --- a/backend/grant/proposal/views.py +++ b/backend/grant/proposal/views.py @@ -6,7 +6,7 @@ from marshmallow import fields, validate from sqlalchemy import or_ from grant.comment.models import Comment, comment_schema, comments_schema -from grant.email.send import send_email +from grant.email.send import send_email, EmailSender from grant.milestone.models import Milestone from grant.parser import body, query, paginated_fields from grant.rfp.models import RFP @@ -366,14 +366,16 @@ def post_proposal_update(proposal_id, title, content): db.session.commit() # Send email to all contributors (even if contribution failed) + email_sender = EmailSender() contributions = ProposalContribution.query.filter_by(proposal_id=proposal_id).all() for c in contributions: if c.user: - send_email(c.user.email_address, 'contribution_update', { + email_sender.add(c.user.email_address, 'contribution_update', { 'proposal': g.current_proposal, 'proposal_update': update, 'update_url': make_url(f'/proposals/{proposal_id}?tab=updates&update={update.id}'), }) + email_sender.start() dumped_update = proposal_update_schema.dump(update) return dumped_update, 201 diff --git a/backend/grant/task/jobs.py b/backend/grant/task/jobs.py index e7d17b89..690c34d2 100644 --- a/backend/grant/task/jobs.py +++ b/backend/grant/task/jobs.py @@ -1,7 +1,7 @@ from datetime import datetime, timedelta from grant.extensions import db -from grant.email.send import send_email +from grant.email.send import send_email, EmailSender from grant.utils.enums import ProposalStage, ContributionStatus from grant.utils.misc import make_url @@ -42,12 +42,12 @@ class ProposalDeadline: def __init__(self, proposal): self.proposal = proposal - + def blobify(self): return { "proposal_id": self.proposal.id, } - + def make_task(self): from .models import Task task = Task( @@ -66,37 +66,40 @@ class ProposalDeadline: # If it was deleted, canceled, or successful, just noop out if not proposal or proposal.is_funded or proposal.stage != ProposalStage.FUNDING_REQUIRED: return - + # Otherwise, mark it as failed and inform everyone proposal.stage = ProposalStage.FAILED db.session.add(proposal) db.session.commit() - # TODO: Bulk-send emails instead of one per email + # Send emails to team & contributors + email_sender = EmailSender() for u in proposal.team: - send_email(u.email_address, 'proposal_failed', { + email_sender.add(u.email_address, 'proposal_failed', { 'proposal': proposal, }) for c in proposal.contributions: if c.user: - send_email(c.user.email_address, 'contribution_proposal_failed', { + email_sender.add(c.user.email_address, 'contribution_proposal_failed', { 'contribution': c, 'proposal': proposal, 'refund_address': c.user.settings.refund_address, 'account_settings_url': make_url('/profile/settings?tab=account') }) + email_sender.start() + class ContributionExpired: JOB_TYPE = 3 def __init__(self, contribution): self.contribution = contribution - + def blobify(self): return { "contribution_id": self.contribution.id, } - + def make_task(self): from .models import Task task = Task( @@ -115,7 +118,7 @@ class ContributionExpired: # If it's missing or not pending, noop out if not contribution or contribution.status != ContributionStatus.PENDING: return - + # Otherwise, inform the user (if not anonymous) if contribution.user: send_email(contribution.user.email_address, 'contribution_expired', { @@ -126,6 +129,7 @@ class ContributionExpired: 'proposal_url': make_url(f'/proposals/{contribution.proposal.id}'), }) + JOBS = { 1: ProposalReminder.process_task, 2: ProposalDeadline.process_task, From c92032e630e2a90e63aff31dac96eef0101e378a Mon Sep 17 00:00:00 2001 From: William O'Beirne Date: Tue, 12 Mar 2019 23:08:50 -0400 Subject: [PATCH 42/72] Add noscript messages (#341) * Noscript warnings * Incomplete sentence. --- frontend/client/components/AuthRoute.tsx | 10 ++++- frontend/client/components/Template/index.tsx | 6 +++ frontend/client/pages/auth.tsx | 9 ++++- frontend/client/pages/create.tsx | 9 ++++- frontend/client/styles/noscript.less | 39 +++++++++++++++++++ frontend/client/styles/style.less | 1 + 6 files changed, 71 insertions(+), 3 deletions(-) create mode 100644 frontend/client/styles/noscript.less diff --git a/frontend/client/components/AuthRoute.tsx b/frontend/client/components/AuthRoute.tsx index c47d7711..a5700095 100644 --- a/frontend/client/components/AuthRoute.tsx +++ b/frontend/client/components/AuthRoute.tsx @@ -53,7 +53,15 @@ class AuthRoute extends React.Component { if (onlyLoggedOut) { newLocation = authForwardLocation || { ...location, pathname: '/profile' }; } - return ; + return ( + <> + + + + ); } } private setAuthForward = () => { diff --git a/frontend/client/components/Template/index.tsx b/frontend/client/components/Template/index.tsx index 8ad5a45b..fb2a7493 100644 --- a/frontend/client/components/Template/index.tsx +++ b/frontend/client/components/Template/index.tsx @@ -29,6 +29,12 @@ export default class Template extends React.PureComponent { ); return ( + {!isHeaderTransparent && ( + + )}
diff --git a/frontend/client/pages/auth.tsx b/frontend/client/pages/auth.tsx index 9bcfc760..11ad0145 100644 --- a/frontend/client/pages/auth.tsx +++ b/frontend/client/pages/auth.tsx @@ -1,6 +1,13 @@ import React from 'react'; import AuthFlow from 'components/AuthFlow'; -const SignInPage = () => ; +const SignInPage = () => ( + <> + + + +); export default SignInPage; diff --git a/frontend/client/pages/create.tsx b/frontend/client/pages/create.tsx index e48b33d3..c429ef03 100644 --- a/frontend/client/pages/create.tsx +++ b/frontend/client/pages/create.tsx @@ -10,7 +10,14 @@ class CreatePage extends React.Component { const { location } = this.props; const parsed = parse(location.search); const rfpId = parsed.rfp ? parseInt(parsed.rfp, 10) : undefined; - return ; + return ( + <> + + + + ); } } diff --git a/frontend/client/styles/noscript.less b/frontend/client/styles/noscript.less new file mode 100644 index 00000000..c435e8b5 --- /dev/null +++ b/frontend/client/styles/noscript.less @@ -0,0 +1,39 @@ +@import 'variables.less'; + +.noScript { + display: block; + + &.is-banner { + padding: 0.25rem 1rem; + text-align: center; + background: @warning-color; + color: #FFF; + } + + &.is-block { + display: flex; + justify-content: flex-start; + align-items: center; + max-width: 420px; + margin: 2rem auto; + padding: 1.5rem; + border: 1px solid @warning-color; + background: rgba(@warning-color, 0.05); + border-radius: 2px; + + &:before { + content: '!'; + display: flex; + justify-content: center; + align-items: center; + flex-shrink: 0; + font-size: 1.8rem; + width: 2.6rem; + height: 2.6rem; + margin-right: 1rem; + border: 2px solid @warning-color; + color: @warning-color; + border-radius: 100%; + } + } +} \ No newline at end of file diff --git a/frontend/client/styles/style.less b/frontend/client/styles/style.less index aa5e0019..ebe67a04 100644 --- a/frontend/client/styles/style.less +++ b/frontend/client/styles/style.less @@ -9,3 +9,4 @@ @import 'react-mde-overrides.less'; @import 'antd-overrides.less'; @import 'html.less'; +@import 'noscript.less'; \ No newline at end of file From 73d087bda74fb7e539da35cac49aaa38b621c354 Mon Sep 17 00:00:00 2001 From: William O'Beirne Date: Tue, 12 Mar 2019 23:35:38 -0400 Subject: [PATCH 43/72] Rate limits (#342) * Implemented rate limits for most POST requests. * Nicer error message for rate limited contributions * Use error message for drafts and invites, limit invites on frontend. --- backend/grant/app.py | 12 ++- backend/grant/extensions.py | 3 + backend/grant/proposal/views.py | 13 ++++ backend/grant/user/views.py | 4 + backend/requirements/prod.txt | 5 +- backend/tests/config.py | 2 + .../components/ContributionModal/index.tsx | 31 +++++--- .../client/components/CreateFlow/Final.tsx | 4 +- .../client/components/CreateFlow/Team.tsx | 75 ++++++++++--------- .../client/components/DraftList/index.tsx | 4 +- 10 files changed, 97 insertions(+), 56 deletions(-) diff --git a/backend/grant/app.py b/backend/grant/app.py index d347aae2..259ecc11 100644 --- a/backend/grant/app.py +++ b/backend/grant/app.py @@ -2,14 +2,14 @@ """The app module, containing the app factory function.""" import sentry_sdk from animal_case import animalify -from flask import Flask, Response, jsonify +from flask import Flask, Response, jsonify, request from flask_cors import CORS from flask_security import SQLAlchemyUserDatastore from flask_sslify import SSLify from sentry_sdk.integrations.flask import FlaskIntegration from grant import commands, proposal, user, comment, milestone, admin, email, blockchain, task, rfp -from grant.extensions import bcrypt, migrate, db, ma, security +from grant.extensions import bcrypt, migrate, db, ma, security, limiter from grant.settings import SENTRY_RELEASE, ENV from grant.utils.auth import AuthException, handle_auth_error, get_authed_user from grant.utils.exceptions import ValidationException @@ -48,7 +48,12 @@ def create_app(config_objects=["grant.settings"]): return jsonify({"message": error_message}), err.code, headers else: return jsonify({"message": error_message}), err.code - + + @app.errorhandler(429) + def handle_limit_error(err): + print(f'Rate limited request to {request.method} {request.path} from ip {request.remote_addr}') + return jsonify({"message": "You’ve done that too many times, please wait and try again later"}), 429 + @app.errorhandler(Exception) def handle_exception(err): return jsonify({"message": "Something went wrong"}), 500 @@ -86,6 +91,7 @@ def register_extensions(app): db.init_app(app) migrate.init_app(app, db) ma.init_app(app) + limiter.init_app(app) user_datastore = SQLAlchemyUserDatastore(db, user.models.User, user.models.Role) security.init_app(app, datastore=user_datastore, register_blueprint=False) diff --git a/backend/grant/extensions.py b/backend/grant/extensions.py index a00f529a..a919ccc9 100644 --- a/backend/grant/extensions.py +++ b/backend/grant/extensions.py @@ -5,9 +5,12 @@ from flask_marshmallow import Marshmallow from flask_migrate import Migrate from flask_security import Security from flask_sqlalchemy import SQLAlchemy +from flask_limiter import Limiter +from flask_limiter.util import get_remote_address bcrypt = Bcrypt() db = SQLAlchemy() migrate = Migrate() ma = Marshmallow() security = Security() +limiter = Limiter(key_func=get_remote_address) diff --git a/backend/grant/proposal/views.py b/backend/grant/proposal/views.py index 89839345..66482646 100644 --- a/backend/grant/proposal/views.py +++ b/backend/grant/proposal/views.py @@ -5,6 +5,7 @@ from flask import Blueprint, g, request from marshmallow import fields, validate from sqlalchemy import or_ +from grant.extensions import limiter from grant.comment.models import Comment, comment_schema, comments_schema from grant.email.send import send_email from grant.milestone.models import Milestone @@ -94,6 +95,7 @@ def report_proposal_comment(proposal_id, comment_id): @blueprint.route("//comments", methods=["POST"]) +@limiter.limit("30/hour;2/minute") @requires_email_verified_auth @body({ "comment": fields.Str(required=True), @@ -174,6 +176,7 @@ def get_proposals(page, filters, search, sort): @blueprint.route("/drafts", methods=["POST"]) +@limiter.limit("10/hour;3/minute") @requires_email_verified_auth @body({ "rfpId": fields.Int(required=False, missing=None) @@ -351,6 +354,7 @@ def get_proposal_update(proposal_id, update_id): @blueprint.route("//updates", methods=["POST"]) +@limiter.limit("5/day;1/minute") @requires_team_member_auth @body({ "title": fields.Str(required=True), @@ -380,11 +384,19 @@ def post_proposal_update(proposal_id, title, content): @blueprint.route("//invite", methods=["POST"]) +@limiter.limit("30/day;10/minute") @requires_team_member_auth @body({ "address": fields.Str(required=True), }) def post_proposal_team_invite(proposal_id, address): + existing_invite = ProposalTeamInvite.query.filter_by( + proposal_id=proposal_id, + address=address + ).first() + if existing_invite: + return {"message": f"You've already invited {address}"}, 400 + invite = ProposalTeamInvite( proposal_id=proposal_id, address=address @@ -472,6 +484,7 @@ def get_proposal_contribution(proposal_id, contribution_id): @blueprint.route("//contributions", methods=["POST"]) +@limiter.limit("30/day;10/hour;2/minute") # TODO add gaurd (minimum, maximum) @body({ "amount": fields.Str(required=True), diff --git a/backend/grant/user/views.py b/backend/grant/user/views.py index 9d510471..72ea3dfe 100644 --- a/backend/grant/user/views.py +++ b/backend/grant/user/views.py @@ -3,6 +3,7 @@ from flask import Blueprint, g from marshmallow import fields import grant.utils.auth as auth +from grant.extensions import limiter from grant.comment.models import Comment, user_comments_schema from grant.email.models import EmailRecovery from grant.parser import query, body @@ -89,6 +90,7 @@ def get_user(user_id, with_proposals, with_comments, with_funded, with_pending, @blueprint.route("/", methods=["POST"]) +@limiter.limit("30/day;5/minute") @body({ # TODO guard all (valid, minimum, maximum) "emailAddress": fields.Str(required=True), @@ -207,6 +209,7 @@ def verify_user_social(service, code): @blueprint.route("/recover", methods=["POST"]) +@limiter.limit("10/day;2/minute") @body({ "email": fields.Str(required=True) }) @@ -239,6 +242,7 @@ def recover_email(code, password): @blueprint.route("/avatar", methods=["POST"]) +@limiter.limit("20/day;3/minute") @auth.requires_auth @body({ "mimetype": fields.Str(required=True) diff --git a/backend/requirements/prod.txt b/backend/requirements/prod.txt index ccbde94f..c25625bd 100644 --- a/backend/requirements/prod.txt +++ b/backend/requirements/prod.txt @@ -75,4 +75,7 @@ webargs==5.1.2 pyotp==2.2.7 # JSON formatting -animal_case==0.4.1 \ No newline at end of file +animal_case==0.4.1 + +# Rate limiting +Flask-Limiter==1.0.1 \ No newline at end of file diff --git a/backend/tests/config.py b/backend/tests/config.py index 2b89a4a1..f1b1688d 100644 --- a/backend/tests/config.py +++ b/backend/tests/config.py @@ -8,6 +8,7 @@ from grant.task.jobs import ProposalReminder from grant.user.models import User, SocialMedia, db, Avatar from grant.settings import PROPOSAL_STAKING_AMOUNT from grant.utils.enums import ProposalStatus +from grant.extensions import limiter from .test_data import test_user, test_other_user, test_proposal, mock_blockchain_api_requests @@ -17,6 +18,7 @@ class BaseTestConfig(TestCase): def create_app(self): app = create_app(['grant.settings', 'tests.settings']) app.config.from_object('tests.settings') + limiter.enabled = False return app def setUp(self): diff --git a/frontend/client/components/ContributionModal/index.tsx b/frontend/client/components/ContributionModal/index.tsx index 4203331a..c82a8386 100644 --- a/frontend/client/components/ContributionModal/index.tsx +++ b/frontend/client/components/ContributionModal/index.tsx @@ -106,7 +106,8 @@ export default class ContributionModal extends React.Component { remain eligible for refunds, you can close this modal, make sure you're logged in, and don't check the "Contribute without attribution" checkbox.

- NOTE: The Zcash Foundation is unable to accept donations of more than $5,000 USD worth of ZEC from anonymous users. + NOTE: The Zcash Foundation is unable to accept donations of more than $5,000 + USD worth of ZEC from anonymous users. } /> @@ -138,16 +139,24 @@ export default class ContributionModal extends React.Component { if (error) { okText = 'Done'; onOk = handleClose; - content = ( - - ); + // This should probably key on non-display text, but oh well. + let title; + let description; + if (error.includes('too many times')) { + title = 'Take it easy!'; + description = ` + We appreciate your enthusiasm, but you've made too many + contributions too fast. Please wait for your other contributions, + and try again later. + `; + } else { + title = 'Something went wrong'; + description = ` + We were unable to get your contribution started. Please check back + soon, we're working to fix the problem as soon as possible. + `; + } + content = ; } else { okText = 'I’ve sent it'; onOk = this.confirmSend; diff --git a/frontend/client/components/CreateFlow/Final.tsx b/frontend/client/components/CreateFlow/Final.tsx index fe36a4cd..9d5ad601 100644 --- a/frontend/client/components/CreateFlow/Final.tsx +++ b/frontend/client/components/CreateFlow/Final.tsx @@ -102,8 +102,8 @@ class CreateFinal extends React.Component { profile's funded tab.

- Once your payment has been sent and processed with 6 confirmations, you - will receive an email. Visit your{' '} + Once your payment has been sent and processed with 6 + confirmations, you will receive an email. Visit your{' '} profile's pending proposals tab {' '} diff --git a/frontend/client/components/CreateFlow/Team.tsx b/frontend/client/components/CreateFlow/Team.tsx index 065ed624..99d70c61 100644 --- a/frontend/client/components/CreateFlow/Team.tsx +++ b/frontend/client/components/CreateFlow/Team.tsx @@ -51,9 +51,10 @@ class CreateFlowTeam extends React.Component { render() { const { team, invites, address } = this.state; - const inviteError = address && !isValidEmail(address) && - 'That doesn’t look like a valid email address'; - const inviteDisabled = !!inviteError || !address; + const inviteError = + address && !isValidEmail(address) && 'That doesn’t look like a valid email address'; + const maxedOut = invites.length >= MAX_TEAM_SIZE - 1; + const inviteDisabled = !!inviteError || !address || maxedOut; const pendingInvites = invites.filter(inv => inv.accepted === null); return ( @@ -79,39 +80,39 @@ class CreateFlowTeam extends React.Component { ))}

)} - {team.length < MAX_TEAM_SIZE && ( -
-

Add a team member

- - - - - - -
- )} + value={address} + onChange={this.handleChangeInviteAddress} + disabled={maxedOut} + /> + + + +
); } @@ -133,7 +134,7 @@ class CreateFlowTeam extends React.Component { }) .catch((err: Error) => { console.error('Failed to send invite', err); - message.error('Failed to send invite', 3); + message.error(err.message, 3); }); }; @@ -146,7 +147,7 @@ class CreateFlowTeam extends React.Component { }) .catch((err: Error) => { console.error('Failed to remove invite', err); - message.error('Failed to remove invite', 3); + message.error(err.message, 3); }); }; } diff --git a/frontend/client/components/DraftList/index.tsx b/frontend/client/components/DraftList/index.tsx index 612ba551..e95aa6b1 100644 --- a/frontend/client/components/DraftList/index.tsx +++ b/frontend/client/components/DraftList/index.tsx @@ -67,10 +67,10 @@ class DraftList extends React.Component { this.setState({ deletingId: null }); } if (deleteDraftError && prevProps.deleteDraftError !== deleteDraftError) { - message.error('Failed to delete draft', 3); + message.error(deleteDraftError, 3); } if (createDraftError && prevProps.createDraftError !== createDraftError) { - message.error('Failed to create draft', 3); + message.error(createDraftError, 3); } } From 4773c4a4ac4148d0922519e6f9195d9a815719ce Mon Sep 17 00:00:00 2001 From: William O'Beirne Date: Wed, 13 Mar 2019 15:20:54 -0400 Subject: [PATCH 44/72] Fix contact url (#350) * Fix contact url * Remove TODO --- frontend/client/components/Profile/ProfileContribution.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frontend/client/components/Profile/ProfileContribution.tsx b/frontend/client/components/Profile/ProfileContribution.tsx index 5863e792..545e777e 100644 --- a/frontend/client/components/Profile/ProfileContribution.tsx +++ b/frontend/client/components/Profile/ProfileContribution.tsx @@ -46,13 +46,12 @@ class ProfileContribution extends React.Component { ); } else if (isExpired) { tag = Expired; - // TODO: Link to support actions = ( <> Delete - Contact support + Contact support ); } else { From 3050caf32952415f18ed0513365149eea1ea293e Mon Sep 17 00:00:00 2001 From: William O'Beirne Date: Wed, 13 Mar 2019 15:40:29 -0400 Subject: [PATCH 45/72] Sentry fixes (#345) * Upgrade sentry sdk, replace all prints with flask logging. * Upgrade node sentry to fix circular json error. * Update frontend sentry versions to maintain parity and nab any fixes. --- backend/grant/app.py | 24 ++++- backend/grant/blockchain/views.py | 4 +- backend/grant/email/send.py | 14 +-- backend/grant/proposal/views.py | 4 +- backend/grant/task/views.py | 8 +- backend/grant/utils/auth.py | 6 +- backend/requirements/prod.txt | 2 +- blockchain/package.json | 2 +- blockchain/yarn.lock | 84 ++++++++++-------- frontend/package.json | 4 +- frontend/yarn.lock | 143 ++++++++++++++++++------------ 11 files changed, 178 insertions(+), 117 deletions(-) diff --git a/backend/grant/app.py b/backend/grant/app.py index 259ecc11..90c02a7d 100644 --- a/backend/grant/app.py +++ b/backend/grant/app.py @@ -1,12 +1,15 @@ # -*- coding: utf-8 -*- """The app module, containing the app factory function.""" import sentry_sdk +import logging +import traceback from animal_case import animalify from flask import Flask, Response, jsonify, request from flask_cors import CORS from flask_security import SQLAlchemyUserDatastore from flask_sslify import SSLify from sentry_sdk.integrations.flask import FlaskIntegration +from sentry_sdk.integrations.logging import LoggingIntegration from grant import commands, proposal, user, comment, milestone, admin, email, blockchain, task, rfp from grant.extensions import bcrypt, migrate, db, ma, security, limiter @@ -40,7 +43,7 @@ def create_app(config_objects=["grant.settings"]): def handle_error(err): headers = err.data.get("headers", None) messages = err.data.get("messages", "Invalid request.") - error_message = "Something went wrong with your request. That's all we know" + error_message = "Something was wrong with your request" if type(messages) == dict: if 'json' in messages: error_message = messages['json'][0] @@ -49,13 +52,24 @@ def create_app(config_objects=["grant.settings"]): else: return jsonify({"message": error_message}), err.code + + @app.errorhandler(404) + def handle_notfound_error(err): + error_message = "Unknown route '{} {}'".format(request.method, request.path) + return jsonify({"message": error_message}), 404 + + @app.errorhandler(429) def handle_limit_error(err): - print(f'Rate limited request to {request.method} {request.path} from ip {request.remote_addr}') + app.logger.warn(f'Rate limited request to {request.method} {request.path} from ip {request.remote_addr}') return jsonify({"message": "You’ve done that too many times, please wait and try again later"}), 429 + @app.errorhandler(Exception) def handle_exception(err): + sentry_sdk.capture_exception(err) + app.logger.debug(traceback.format_exc()) + app.logger.debug("Uncaught exception at {} {}, see above for traceback".format(request.method, request.path)) return jsonify({"message": "Something went wrong"}), 500 for conf in config_objects: @@ -67,10 +81,14 @@ def create_app(config_objects=["grant.settings"]): register_commands(app) if not app.config.get("TESTING"): + sentry_logging = LoggingIntegration( + level=logging.INFO, + event_level=logging.ERROR + ) sentry_sdk.init( environment=ENV, release=SENTRY_RELEASE, - integrations=[FlaskIntegration()] + integrations=[FlaskIntegration(), sentry_logging] ) # handle all AuthExceptions thusly diff --git a/backend/grant/blockchain/views.py b/backend/grant/blockchain/views.py index 8216c1c8..80869e58 100644 --- a/backend/grant/blockchain/views.py +++ b/backend/grant/blockchain/views.py @@ -1,4 +1,4 @@ -from flask import Blueprint +from flask import Blueprint, current_app from grant.blockchain.bootstrap import send_bootstrap_data from grant.utils.auth import internal_webhook @@ -9,6 +9,6 @@ blueprint = Blueprint("blockchain", __name__, url_prefix="/api/v1/blockchain") @blueprint.route("/bootstrap", methods=["GET"]) @internal_webhook def get_bootstrap_info(): - print('Bootstrap data requested from blockchain watcher microservice...') + current_app.logger.info('Bootstrap data requested from blockchain watcher microservice...') send_bootstrap_data() return {"message": "ok"}, 200 diff --git a/backend/grant/email/send.py b/backend/grant/email/send.py index d56b79ff..9202d526 100644 --- a/backend/grant/email/send.py +++ b/backend/grant/email/send.py @@ -1,5 +1,6 @@ import sendgrid from flask import render_template, Markup, current_app +from sentry_sdk import capture_exception from grant.settings import SENDGRID_API_KEY, SENDGRID_DEFAULT_FROM, UI from grant.utils.misc import make_url from python_http_client import HTTPError @@ -359,7 +360,7 @@ def send_email(to, type, email_args): if user and 'subscription' in info: sub = info['subscription'] if user and not is_subscribed(user.settings.email_subscriptions, sub): - print(f'Ignoring send_email to {to} of type {type} because user is unsubscribed.') + app.logger.debug(f'Ignoring send_email to {to} of type {type} because user is unsubscribed.') return try: @@ -375,9 +376,12 @@ def send_email(to, type, email_args): mail.add_content(Content('text/html', email['html'])) res = sg.client.mail.send.post(request_body=mail.get()) - print('Just sent an email to %s of type %s, response code: %s' % (to, type, res.status_code)) + current_app.logger.info('Just sent an email to %s of type %s, response code: %s' % (to, type, res.status_code)) except HTTPError as e: - print('An HTTP error occured while sending an email to %s - %s: %s' % (to, e.__class__.__name__, e)) - print(e.body) + current_app.logger.info('An HTTP error occured while sending an email to %s - %s: %s' % (to, e.__class__.__name__, e)) + current_app.logger.debug(e.body) + capture_exception(e) except Exception as e: - print('An unknown error occured while sending an email to %s - %s: %s' % (to, e.__class__.__name__, e)) + current_app.logger.info('An unknown error occured while sending an email to %s - %s: %s' % (to, e.__class__.__name__, e)) + current_app.logger.debug(e) + capture_exception(e) diff --git a/backend/grant/proposal/views.py b/backend/grant/proposal/views.py index 66482646..bb180a01 100644 --- a/backend/grant/proposal/views.py +++ b/backend/grant/proposal/views.py @@ -1,7 +1,7 @@ from datetime import datetime from decimal import Decimal -from flask import Blueprint, g, request +from flask import Blueprint, g, request, current_app from marshmallow import fields, validate from sqlalchemy import or_ @@ -530,7 +530,7 @@ def post_contribution_confirmation(contribution_id, to, amount, txid): if not contribution: # TODO: Log in sentry - print(f'Unknown contribution {contribution_id} confirmed with txid {txid}') + current_app.logger.warn(f'Unknown contribution {contribution_id} confirmed with txid {txid}') return {"message": "No contribution matching id"}, 404 if contribution.status == ContributionStatus.CONFIRMED: diff --git a/backend/grant/task/views.py b/backend/grant/task/views.py index c1f14908..28369826 100644 --- a/backend/grant/task/views.py +++ b/backend/grant/task/views.py @@ -1,6 +1,8 @@ from datetime import datetime +from flask import Blueprint, jsonify, current_app +from sentry_sdk import capture_exception +from traceback import format_exc -from flask import Blueprint, jsonify from grant.task.jobs import JOBS from grant.task.models import Task, tasks_schema from grant.extensions import db @@ -17,7 +19,7 @@ def task(): each_task.completed = True db.session.add(each_task) except Exception as e: - # replace with Sentry logging - print("Oops, something went wrong: {}".format(e)) + current_app.logger.info("Task #{} failed: {}".format(each_task.id, e)) + capture_exception(e) db.session.commit() return jsonify(tasks_schema.dump(tasks)) diff --git a/backend/grant/utils/auth.py b/backend/grant/utils/auth.py index 68bab2d5..88fb6985 100644 --- a/backend/grant/utils/auth.py +++ b/backend/grant/utils/auth.py @@ -2,7 +2,7 @@ from functools import wraps from datetime import datetime, timedelta import sentry_sdk -from flask import request, g, jsonify, session +from flask import request, g, jsonify, session, current_app from flask_security.core import current_user from flask_security.utils import logout_user from grant.proposal.models import Proposal @@ -156,10 +156,10 @@ def internal_webhook(f): def decorated(*args, **kwargs): secret = request.headers.get('authorization') if not secret: - print('Internal webhook missing "Authorization" header') + current_app.logger.warn('Internal webhook missing "Authorization" header') return jsonify(message="Invalid 'Authorization' header"), 403 if BLOCKCHAIN_API_SECRET not in secret: - print(f'Internal webhook provided invalid "Authorization" header: {secret}') + current_app.logger.warn(f'Internal webhook provided invalid "Authorization" header: {secret}') return jsonify(message="Invalid 'Authorization' header"), 403 return f(*args, **kwargs) diff --git a/backend/requirements/prod.txt b/backend/requirements/prod.txt index c25625bd..39ad3deb 100644 --- a/backend/requirements/prod.txt +++ b/backend/requirements/prod.txt @@ -54,7 +54,7 @@ markdownify sendgrid==5.6.0 #sentry -sentry-sdk[flask]==0.5.5 +sentry-sdk[flask]==0.7.6 #boto3 (AWS sdk) boto3==1.9.52 diff --git a/blockchain/package.json b/blockchain/package.json index 9aa91ec6..d3c086fa 100755 --- a/blockchain/package.json +++ b/blockchain/package.json @@ -30,7 +30,7 @@ "typescript": "^3.2.1" }, "dependencies": { - "@sentry/node": "4.4.2", + "@sentry/node": "4.6.4", "@types/cors": "2.8.4", "@types/dotenv": "^6.1.0", "@types/ws": "^6.0.1", diff --git a/blockchain/yarn.lock b/blockchain/yarn.lock index 1810869e..ce57fd57 100755 --- a/blockchain/yarn.lock +++ b/blockchain/yarn.lock @@ -2,62 +2,63 @@ # yarn lockfile v1 -"@sentry/core@4.4.2": - version "4.4.2" - resolved "https://registry.yarnpkg.com/@sentry/core/-/core-4.4.2.tgz#562526bc634c087f04bbca68b09cedc4b41cc64d" - integrity sha512-hJyAodTCf4sZfVdf41Rtuzj4EsyzYq5rdMZ+zc2Vinwdf8D0/brHe91fHeO0CKXEb2P0wJsrjwMidG/ccq/M8A== +"@sentry/core@4.6.4": + version "4.6.4" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-4.6.4.tgz#7236e08115423b81b96a13c2c37f29bcc1477745" + integrity sha512-NGl2nkAaQ8dGqJAMS1Hb+7RyVjW4tmCbK6d7H/zKnOpBuU+qSW4XCm2NoGLLa8qb4SZUPIBRv6U0ByvEQlGtqw== dependencies: - "@sentry/hub" "4.4.2" - "@sentry/minimal" "4.4.2" - "@sentry/types" "4.4.2" - "@sentry/utils" "4.4.2" + "@sentry/hub" "4.6.4" + "@sentry/minimal" "4.6.4" + "@sentry/types" "4.5.3" + "@sentry/utils" "4.6.4" tslib "^1.9.3" -"@sentry/hub@4.4.2": - version "4.4.2" - resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-4.4.2.tgz#1399556fda06fb83c4f186c4aa842725f520159c" - integrity sha512-oe9ytXkTWyD+QmOpVzHAqTbRV4Hc0ee2Nt6HvrDtRmlXzQxfvTWG2F8KYT6w8kzqg5klnuRpnsmgTTV3KuNBVQ== +"@sentry/hub@4.6.4": + version "4.6.4" + resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-4.6.4.tgz#2bd5d67ccd43d4f5afc45005a330a11b14d46cea" + integrity sha512-R3ACxUZbrAMP6vyIvt1k4bE3OIyg1CzbEhzknKljPrk1abVmJVP7W/X1vBysdRtI3m/9RjOSO7Lxx3XXqoHoQg== dependencies: - "@sentry/types" "4.4.2" - "@sentry/utils" "4.4.2" + "@sentry/types" "4.5.3" + "@sentry/utils" "4.6.4" tslib "^1.9.3" -"@sentry/minimal@4.4.2": - version "4.4.2" - resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-4.4.2.tgz#13fffc6b17a2401b6a79947838a637626ab80b10" - integrity sha512-GEZZiNvVgqFAESZhAe3vjwTInn13lI2bSI3ItQN4RUWKL/W4n/fwVoDJbkb1U8aWxanuMnRDEpKwyQv6zYTZfw== +"@sentry/minimal@4.6.4": + version "4.6.4" + resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-4.6.4.tgz#dc4bb47df90dad6025d832852ac11fe29ed50147" + integrity sha512-jZa9mfzDzJI98tg6uxFG3gdVLyz0nOHpLP9H8Kn/BelZ7WEG/ogB8PDi1hI9JvCTXAr8kV81mEecldADa9L9Yg== dependencies: - "@sentry/hub" "4.4.2" - "@sentry/types" "4.4.2" + "@sentry/hub" "4.6.4" + "@sentry/types" "4.5.3" tslib "^1.9.3" -"@sentry/node@4.4.2": - version "4.4.2" - resolved "https://registry.yarnpkg.com/@sentry/node/-/node-4.4.2.tgz#549921d2df3cbf58ebcfb525c3005c3fec4739a3" - integrity sha512-8/KlSdfVhledZ6PS6muxZY5r2pqhw8MNSXP7AODR2qRrHwsbnirVgV21WIAYAjKXEfYQGbm69lyoaTJGazlQ3Q== +"@sentry/node@4.6.4": + version "4.6.4" + resolved "https://registry.yarnpkg.com/@sentry/node/-/node-4.6.4.tgz#933c2e3ce93bc7861de6d4310ed1fe66f85da301" + integrity sha512-nfaLB+cE0dddjWD0yI0nB/UqXkPw/6FKDRpB1NZ61amAM4QRXa4hRTdHvqjUovzV/5/pVMQYsOyCk0pNWMtMUQ== dependencies: - "@sentry/core" "4.4.2" - "@sentry/hub" "4.4.2" - "@sentry/types" "4.4.2" - "@sentry/utils" "4.4.2" + "@sentry/core" "4.6.4" + "@sentry/hub" "4.6.4" + "@sentry/types" "4.5.3" + "@sentry/utils" "4.6.4" "@types/stack-trace" "0.0.29" cookie "0.3.1" - https-proxy-agent "^2.2.1" + https-proxy-agent "2.2.1" + lru_map "0.3.3" lsmod "1.0.0" stack-trace "0.0.10" tslib "^1.9.3" -"@sentry/types@4.4.2": - version "4.4.2" - resolved "https://registry.yarnpkg.com/@sentry/types/-/types-4.4.2.tgz#f38dd3bc671cd2f5983a85553aebeac9c2286b17" - integrity sha512-QyQd6PKKIyjJgaq/RQjsxPJEWbXcuiWZ9RvSnhBjS5jj53HEzkM1qkbAFqlYHJ1DTJJ1EuOM4+aTmGzHe93zuA== +"@sentry/types@4.5.3": + version "4.5.3" + resolved "https://registry.yarnpkg.com/@sentry/types/-/types-4.5.3.tgz#3350dce2b7f9b936a8c327891c12e3aef7bd8852" + integrity sha512-7ll1PAFNjrBNX9rzy3P2qAQrpQwHaDO3uKj735qsnGw34OtAS8Xr8WYrjI14f9fMPa/XIeWvMPb4GMic28V/ag== -"@sentry/utils@4.4.2": - version "4.4.2" - resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-4.4.2.tgz#e05a47e135ecef29e63a996f59aee8c8f792c222" - integrity sha512-j/Ad8G1abHlJdD2q7aWWbSOSeWB5M5v1R1VKL8YPlwEbSvvmEQWePhBKFI0qlnKd2ObdUQsj86pHEXJRSFNfCw== +"@sentry/utils@4.6.4": + version "4.6.4" + resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-4.6.4.tgz#ca254c142b519b4f20d63c2f9edf1a89966be36f" + integrity sha512-Tc5R46z7ve9Z+uU34ceDoEUR7skfQgXVIZqjbrTQphgm6EcMSNdRfkK3SJYZL5MNKiKhb7Tt/O3aPBy5bTZy6w== dependencies: - "@sentry/types" "4.4.2" + "@sentry/types" "4.5.3" tslib "^1.9.3" "@types/body-parser@*", "@types/body-parser@1.17.0": @@ -1005,7 +1006,7 @@ http-errors@1.6.3, http-errors@~1.6.2, http-errors@~1.6.3: setprototypeof "1.1.0" statuses ">= 1.4.0 < 2" -https-proxy-agent@^2.2.1: +https-proxy-agent@2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz#51552970fa04d723e04c56d04178c3f92592bbc0" integrity sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ== @@ -1320,6 +1321,11 @@ lru-cache@^4.0.1: pseudomap "^1.0.2" yallist "^2.1.2" +lru_map@0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/lru_map/-/lru_map-0.3.3.tgz#b5c8351b9464cbd750335a79650a0ec0e56118dd" + integrity sha1-tcg1G5Rky9dQM1p5ZQoOwOVhGN0= + lsmod@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/lsmod/-/lsmod-1.0.0.tgz#9a00f76dca36eb23fa05350afe1b585d4299e64b" diff --git a/frontend/package.json b/frontend/package.json index 90231faf..af5c11d9 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -42,8 +42,8 @@ "@loadable/component": "5.5.0", "@loadable/server": "5.5.0", "@loadable/webpack-plugin": "5.5.0", - "@sentry/browser": "^4.3.2", - "@sentry/node": "^4.3.2", + "@sentry/browser": "4.6.4", + "@sentry/node": "4.6.4", "@svgr/webpack": "^2.4.0", "@types/classnames": "^2.2.6", "@types/cors": "^2.8.4", diff --git a/frontend/yarn.lock b/frontend/yarn.lock index f64632e5..fc0380f4 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -1487,59 +1487,74 @@ dependencies: any-observable "^0.3.0" -"@sentry/browser@^4.3.2": - version "4.3.2" - resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-4.3.2.tgz#430b83583c5c25d33041dd80bf6ed19216086f70" +"@sentry/browser@4.6.4": + version "4.6.4" + resolved "https://registry.yarnpkg.com/@sentry/browser/-/browser-4.6.4.tgz#94e376be7bb313b6faf9e40950405897dd1c1605" + integrity sha512-w2ITpQbs2vTKS5vtPXDgeDyr+5C4lCnTXugJrqn8u8w/XaDb3vRogfMWpQcaUENllO5xdZSItSAAHsQucY/LvA== dependencies: - "@sentry/core" "4.3.2" - "@sentry/types" "4.3.2" - "@sentry/utils" "4.3.2" + "@sentry/core" "4.6.4" + "@sentry/types" "4.5.3" + "@sentry/utils" "4.6.4" + tslib "^1.9.3" -"@sentry/core@4.3.2": - version "4.3.2" - resolved "https://registry.yarnpkg.com/@sentry/core/-/core-4.3.2.tgz#e8a2850a11316d865ed7d3030ee2c4a1608bb1d8" +"@sentry/core@4.6.4": + version "4.6.4" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-4.6.4.tgz#7236e08115423b81b96a13c2c37f29bcc1477745" + integrity sha512-NGl2nkAaQ8dGqJAMS1Hb+7RyVjW4tmCbK6d7H/zKnOpBuU+qSW4XCm2NoGLLa8qb4SZUPIBRv6U0ByvEQlGtqw== dependencies: - "@sentry/hub" "4.3.2" - "@sentry/minimal" "4.3.2" - "@sentry/types" "4.3.2" - "@sentry/utils" "4.3.2" + "@sentry/hub" "4.6.4" + "@sentry/minimal" "4.6.4" + "@sentry/types" "4.5.3" + "@sentry/utils" "4.6.4" + tslib "^1.9.3" -"@sentry/hub@4.3.2": - version "4.3.2" - resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-4.3.2.tgz#1ea10038e2080035d2bc09f5f26829cd106a1516" +"@sentry/hub@4.6.4": + version "4.6.4" + resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-4.6.4.tgz#2bd5d67ccd43d4f5afc45005a330a11b14d46cea" + integrity sha512-R3ACxUZbrAMP6vyIvt1k4bE3OIyg1CzbEhzknKljPrk1abVmJVP7W/X1vBysdRtI3m/9RjOSO7Lxx3XXqoHoQg== dependencies: - "@sentry/types" "4.3.2" - "@sentry/utils" "4.3.2" + "@sentry/types" "4.5.3" + "@sentry/utils" "4.6.4" + tslib "^1.9.3" -"@sentry/minimal@4.3.2": - version "4.3.2" - resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-4.3.2.tgz#c0958e5858b2105a6a0b523787e459a03af603cc" +"@sentry/minimal@4.6.4": + version "4.6.4" + resolved "https://registry.yarnpkg.com/@sentry/minimal/-/minimal-4.6.4.tgz#dc4bb47df90dad6025d832852ac11fe29ed50147" + integrity sha512-jZa9mfzDzJI98tg6uxFG3gdVLyz0nOHpLP9H8Kn/BelZ7WEG/ogB8PDi1hI9JvCTXAr8kV81mEecldADa9L9Yg== dependencies: - "@sentry/hub" "4.3.2" - "@sentry/types" "4.3.2" + "@sentry/hub" "4.6.4" + "@sentry/types" "4.5.3" + tslib "^1.9.3" -"@sentry/node@^4.3.2": - version "4.3.2" - resolved "https://registry.yarnpkg.com/@sentry/node/-/node-4.3.2.tgz#3c7cd3aff238f3b1eb3252147b963cbdf520aa45" +"@sentry/node@4.6.4": + version "4.6.4" + resolved "https://registry.yarnpkg.com/@sentry/node/-/node-4.6.4.tgz#933c2e3ce93bc7861de6d4310ed1fe66f85da301" + integrity sha512-nfaLB+cE0dddjWD0yI0nB/UqXkPw/6FKDRpB1NZ61amAM4QRXa4hRTdHvqjUovzV/5/pVMQYsOyCk0pNWMtMUQ== dependencies: - "@sentry/core" "4.3.2" - "@sentry/hub" "4.3.2" - "@sentry/types" "4.3.2" - "@sentry/utils" "4.3.2" + "@sentry/core" "4.6.4" + "@sentry/hub" "4.6.4" + "@sentry/types" "4.5.3" + "@sentry/utils" "4.6.4" + "@types/stack-trace" "0.0.29" cookie "0.3.1" + https-proxy-agent "2.2.1" + lru_map "0.3.3" lsmod "1.0.0" - md5 "2.2.1" stack-trace "0.0.10" + tslib "^1.9.3" -"@sentry/types@4.3.2": - version "4.3.2" - resolved "https://registry.yarnpkg.com/@sentry/types/-/types-4.3.2.tgz#28b143979482fcbc9f9e520250482dde015b13fa" +"@sentry/types@4.5.3": + version "4.5.3" + resolved "https://registry.yarnpkg.com/@sentry/types/-/types-4.5.3.tgz#3350dce2b7f9b936a8c327891c12e3aef7bd8852" + integrity sha512-7ll1PAFNjrBNX9rzy3P2qAQrpQwHaDO3uKj735qsnGw34OtAS8Xr8WYrjI14f9fMPa/XIeWvMPb4GMic28V/ag== -"@sentry/utils@4.3.2": - version "4.3.2" - resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-4.3.2.tgz#de14046eba972af9d62508f78cd998b0352d634a" +"@sentry/utils@4.6.4": + version "4.6.4" + resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-4.6.4.tgz#ca254c142b519b4f20d63c2f9edf1a89966be36f" + integrity sha512-Tc5R46z7ve9Z+uU34ceDoEUR7skfQgXVIZqjbrTQphgm6EcMSNdRfkK3SJYZL5MNKiKhb7Tt/O3aPBy5bTZy6w== dependencies: - "@sentry/types" "4.3.2" + "@sentry/types" "4.5.3" + tslib "^1.9.3" "@storybook/addons@4.0.0-alpha.22": version "4.0.0-alpha.22" @@ -2001,6 +2016,11 @@ version "1.7.5" resolved "https://registry.yarnpkg.com/@types/showdown/-/showdown-1.7.5.tgz#91061f2f16d5bdf66b186185999ed675a8908b6a" +"@types/stack-trace@0.0.29": + version "0.0.29" + resolved "https://registry.yarnpkg.com/@types/stack-trace/-/stack-trace-0.0.29.tgz#eb7a7c60098edb35630ed900742a5ecb20cfcb4d" + integrity sha512-TgfOX+mGY/NyNxJLIbDWrO9DjGoVSW9+aB8H2yy1fy32jsvxijhmyJI9fDFgvz3YP4lvJaq9DzdR/M1bOgVc9g== + "@types/storybook__react@^3.0.9": version "3.0.9" resolved "https://registry.yarnpkg.com/@types/storybook__react/-/storybook__react-3.0.9.tgz#aac193f0c037ddfc2342bd027d720553ac089a56" @@ -2243,6 +2263,13 @@ address@1.0.3, address@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/address/-/address-1.0.3.tgz#b5f50631f8d6cec8bd20c963963afb55e06cbce9" +agent-base@^4.1.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-4.2.1.tgz#d89e5999f797875674c07d87f260fc41e83e8ca9" + integrity sha512-JVwXMr9nHYTUXsBFKUqhJwvlcYU/blreOEUkhNR2eXZIvwd+c+o5V4MgDPKWnMS/56awN3TRzIP+KoPn+roQtg== + dependencies: + es6-promisify "^5.0.0" + "airbnb-js-shims@^1 || ^2": version "2.1.1" resolved "https://registry.yarnpkg.com/airbnb-js-shims/-/airbnb-js-shims-2.1.1.tgz#a509611480db7e6d9db62fe2acfaeb473b6842ac" @@ -3444,10 +3471,6 @@ chardet@^0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e" -charenc@~0.0.1: - version "0.0.2" - resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667" - check-types@^7.3.0: version "7.4.0" resolved "https://registry.yarnpkg.com/check-types/-/check-types-7.4.0.tgz#0378ec1b9616ec71f774931a3c6516fad8c152f4" @@ -3988,10 +4011,6 @@ cross-spawn@^3.0.0: lru-cache "^4.0.1" which "^1.2.9" -crypt@~0.0.1: - version "0.0.2" - resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b" - crypto-browserify@^3.11.0: version "3.12.0" resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" @@ -4784,6 +4803,13 @@ es6-promise@^4.0.3: version "4.1.1" resolved "https://registry.yarnpkg.com/es6-promise/-/es6-promise-4.1.1.tgz#8811e90915d9a0dba36274f0b242dbda78f9c92a" +es6-promisify@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/es6-promisify/-/es6-promisify-5.0.0.tgz#5109d62f3e56ea967c4b63505aef08291c8a5203" + integrity sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM= + dependencies: + es6-promise "^4.0.3" + es6-shim@^0.35.3: version "0.35.3" resolved "https://registry.yarnpkg.com/es6-shim/-/es6-shim-0.35.3.tgz#9bfb7363feffff87a6cdb6cd93e405ec3c4b6f26" @@ -6152,6 +6178,14 @@ https-browserify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" +https-proxy-agent@2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-2.2.1.tgz#51552970fa04d723e04c56d04178c3f92592bbc0" + integrity sha512-HPCTS1LW51bcyMYbxUIOO4HEOlQ1/1qRaFWcyxvwaqUS9TY88aoEuHUY33kuAh1YhVVaDQhLZsnPd+XNARWZlQ== + dependencies: + agent-base "^4.1.0" + debug "^3.1.0" + https-proxy@0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/https-proxy/-/https-proxy-0.0.2.tgz#9e7d542f1ce8d37c06e1f940a8a9a227bb48ddf0" @@ -6488,7 +6522,7 @@ is-binary-path@^1.0.0: dependencies: binary-extensions "^1.0.0" -is-buffer@^1.0.2, is-buffer@^1.1.5, is-buffer@~1.1.1: +is-buffer@^1.0.2, is-buffer@^1.1.5: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" @@ -7437,6 +7471,11 @@ lru-cache@^4.1.1: pseudomap "^1.0.2" yallist "^2.1.2" +lru_map@0.3.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/lru_map/-/lru_map-0.3.3.tgz#b5c8351b9464cbd750335a79650a0ec0e56118dd" + integrity sha1-tcg1G5Rky9dQM1p5ZQoOwOVhGN0= + lsmod@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/lsmod/-/lsmod-1.0.0.tgz#9a00f76dca36eb23fa05350afe1b585d4299e64b" @@ -7493,14 +7532,6 @@ md5.js@^1.3.4: hash-base "^3.0.0" inherits "^2.0.1" -md5@2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/md5/-/md5-2.2.1.tgz#53ab38d5fe3c8891ba465329ea23fac0540126f9" - dependencies: - charenc "~0.0.1" - crypt "~0.0.1" - is-buffer "~1.1.1" - mdn-data@~1.1.0: version "1.1.4" resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-1.1.4.tgz#50b5d4ffc4575276573c4eedb8780812a8419f01" @@ -11341,7 +11372,7 @@ tslib@1.9.0: version "1.9.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.0.tgz#e37a86fda8cbbaf23a057f473c9f4dc64e5fc2e8" -tslib@^1.7.1, tslib@^1.8.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.2: +tslib@^1.7.1, tslib@^1.8.0, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.2, tslib@^1.9.3: version "1.9.3" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286" From 0b3bf5e66ff383d7bd672d7e957c62b523dc2c00 Mon Sep 17 00:00:00 2001 From: William O'Beirne Date: Wed, 13 Mar 2019 15:41:30 -0400 Subject: [PATCH 46/72] Update email from & name (#348) --- backend/grant/email/send.py | 8 ++++---- backend/grant/settings.py | 3 ++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/backend/grant/email/send.py b/backend/grant/email/send.py index 9202d526..5bfde741 100644 --- a/backend/grant/email/send.py +++ b/backend/grant/email/send.py @@ -1,7 +1,7 @@ import sendgrid from flask import render_template, Markup, current_app +from grant.settings import SENDGRID_API_KEY, SENDGRID_DEFAULT_FROM, SENDGRID_DEFAULT_FROMNAME, UI from sentry_sdk import capture_exception -from grant.settings import SENDGRID_API_KEY, SENDGRID_DEFAULT_FROM, UI from grant.utils.misc import make_url from python_http_client import HTTPError from sendgrid.helpers.mail import Email, Mail, Content @@ -34,8 +34,8 @@ def team_invite_info(email_args): def recover_info(email_args): return { - 'subject': '{} account recovery'.format(UI['NAME']), - 'title': '{} account recovery'.format(UI['NAME']), + 'subject': 'Recover your account', + 'title': 'Recover your account', 'preview': 'Use the link to recover your account.' } @@ -368,7 +368,7 @@ def send_email(to, type, email_args): sg = sendgrid.SendGridAPIClient(apikey=SENDGRID_API_KEY) mail = Mail( - from_email=Email(SENDGRID_DEFAULT_FROM), + from_email=Email(SENDGRID_DEFAULT_FROM, SENDGRID_DEFAULT_FROMNAME), to_email=Email(to), subject=email['info']['subject'], ) diff --git a/backend/grant/settings.py b/backend/grant/settings.py index cabb52eb..9f99d3fb 100644 --- a/backend/grant/settings.py +++ b/backend/grant/settings.py @@ -29,7 +29,8 @@ SQLALCHEMY_TRACK_MODIFICATIONS = False SESSION_COOKIE_DOMAIN = env.str('SESSION_COOKIE_DOMAIN', default=None) SENDGRID_API_KEY = env.str("SENDGRID_API_KEY", default="") -SENDGRID_DEFAULT_FROM = "noreply@zfnd.org" +SENDGRID_DEFAULT_FROM = "noreply@grants.zfnd.org" +SENDGRID_DEFAULT_FROMNAME = "ZF Grants" SENTRY_DSN = env.str("SENTRY_DSN", default=None) SENTRY_RELEASE = env.str("SENTRY_RELEASE", default=None) From 1ae519e251afa33a64703ce96338644ff5653d45 Mon Sep 17 00:00:00 2001 From: AMStrix Date: Wed, 13 Mar 2019 16:39:50 -0500 Subject: [PATCH 47/72] Address TODOs (#349) * todos: simple ones, removals & modifications to NOTE * rem-todo: reduced markdown images are removed by sanitizer * be todo: add user validation to create * be todo: improve test_invide_api tests * be todo: remove todo comment * fe todo: set error messages on reducers * fe todo: upgrade and enable react-helmet * todos - remove uneeded * fe todos: remove unecessary * be: fix remaining staking contribution calculation --- admin/src/components/ProposalDetail/index.tsx | 3 - backend/grant/milestone/models.py | 1 - backend/grant/proposal/models.py | 17 +++-- backend/grant/proposal/views.py | 11 ++- backend/grant/rfp/models.py | 2 +- backend/grant/task/jobs.py | 1 - backend/grant/user/models.py | 25 +++++-- backend/tests/admin/test_admin_api.py | 32 ++++---- backend/tests/user/test_invite_api.py | 9 ++- backend/tests/user/test_user_api.py | 2 +- blockchain/src/node.ts | 73 +++++++++++-------- blockchain/src/util.ts | 20 ++--- e2e/README.md | 2 +- .../create.fund.ms2.no-vote.re-vote.spec.ts | 1 - frontend/client/components/BasicHead.tsx | 2 - frontend/client/components/Comment/index.tsx | 1 - frontend/client/components/HeaderDetails.tsx | 41 +++++------ frontend/client/components/Profile/index.tsx | 1 - .../Proposal/CampaignBlock/index.tsx | 1 - .../components/Proposal/Comments/index.tsx | 1 - frontend/client/modules/proposals/reducers.ts | 6 +- frontend/client/modules/rfps/reducers.ts | 3 +- frontend/client/utils/api.ts | 2 +- frontend/client/utils/markdown.ts | 1 - frontend/package.json | 4 +- frontend/server/components/HTML.tsx | 9 +-- frontend/types/contribution.ts | 2 +- frontend/types/user.ts | 2 +- frontend/yarn.lock | 21 ++++-- 29 files changed, 159 insertions(+), 137 deletions(-) diff --git a/admin/src/components/ProposalDetail/index.tsx b/admin/src/components/ProposalDetail/index.tsx index 2f54d3ea..65f6f4df 100644 --- a/admin/src/components/ProposalDetail/index.tsx +++ b/admin/src/components/ProposalDetail/index.tsx @@ -401,7 +401,6 @@ class ProposalDetailNaked extends React.Component { - {/* TODO - comments, milestones, updates &etc. */}
{JSON.stringify(p, null, 4)}
@@ -469,8 +468,6 @@ class ProposalDetailNaked extends React.Component { ))} - - {/* TODO: contributors here? */} diff --git a/backend/grant/milestone/models.py b/backend/grant/milestone/models.py index 9f92ae9a..16bf262c 100644 --- a/backend/grant/milestone/models.py +++ b/backend/grant/milestone/models.py @@ -22,7 +22,6 @@ class Milestone(db.Model): content = db.Column(db.Text, nullable=False) payout_percent = db.Column(db.String(255), nullable=False) immediate_payout = db.Column(db.Boolean) - # TODO: change to estimated_duration (sec or ms) -- FE can calc from dates on draft date_estimated = db.Column(db.DateTime, nullable=False) stage = db.Column(db.String(255), nullable=False) diff --git a/backend/grant/proposal/models.py b/backend/grant/proposal/models.py index a23ba7c6..eb503aa4 100644 --- a/backend/grant/proposal/models.py +++ b/backend/grant/proposal/models.py @@ -376,14 +376,14 @@ class Proposal(db.Model): def get_staking_contribution(self, user_id: int): contribution = None - remaining = PROPOSAL_STAKING_AMOUNT - Decimal(self.contributed) + remaining = PROPOSAL_STAKING_AMOUNT - Decimal(self.amount_staked) # check funding if remaining > 0: # find pending contribution for any user of remaining amount - # TODO: Filter by staking=True? contribution = ProposalContribution.query.filter_by( proposal_id=self.id, status=ProposalStatus.PENDING, + staking=True, ).first() if not contribution: contribution = self.create_contribution( @@ -537,6 +537,14 @@ class Proposal(db.Model): funded = reduce(lambda prev, c: prev + Decimal(c.amount), contributions, 0) return str(funded) + @hybrid_property + def amount_staked(self): + contributions = ProposalContribution.query \ + .filter_by(proposal_id=self.id, status=ContributionStatus.CONFIRMED, staking=True) \ + .all() + amount = reduce(lambda prev, c: prev + Decimal(c.amount), contributions, 0) + return str(amount) + @hybrid_property def funded(self): target = Decimal(self.target) @@ -716,9 +724,6 @@ proposal_team_invite_schema = ProposalTeamInviteSchema() proposal_team_invites_schema = ProposalTeamInviteSchema(many=True) -# TODO: Find a way to extend ProposalTeamInviteSchema instead of redefining - - class InviteWithProposalSchema(ma.Schema): class Meta: model = ProposalTeamInvite @@ -768,7 +773,7 @@ class ProposalContributionSchema(ma.Schema): def get_addresses(self, obj): # Omit 'memo' and 'sprout' for now - # TODO: Add back in 'sapling' when ready + # NOTE: Add back in 'sapling' when ready addresses = blockchain_get('/contribution/addresses', {'contributionId': obj.id}) return { 'transparent': addresses['transparent'], diff --git a/backend/grant/proposal/views.py b/backend/grant/proposal/views.py index 8dcda1bc..d4a857f3 100644 --- a/backend/grant/proposal/views.py +++ b/backend/grant/proposal/views.py @@ -4,6 +4,7 @@ from decimal import Decimal from flask import Blueprint, g, request, current_app from marshmallow import fields, validate from sqlalchemy import or_ +from sentry_sdk import capture_message from grant.extensions import limiter from grant.comment.models import Comment, comment_schema, comments_schema @@ -136,7 +137,7 @@ def post_proposal_comments(proposal_id, comment, parent_comment_id): db.session.commit() dumped_comment = comment_schema.dump(comment) - # TODO: Email proposal team if top-level comment + # Email proposal team if top-level comment if not parent: for member in proposal.team: send_email(member.email_address, 'proposal_comment', { @@ -407,7 +408,6 @@ def post_proposal_team_invite(proposal_id, address): db.session.commit() # Send email - # TODO: Move this to some background task / after request action email = address user = User.get_by_email(email_address=address) if user: @@ -531,8 +531,9 @@ def post_contribution_confirmation(contribution_id, to, amount, txid): id=contribution_id).first() if not contribution: - # TODO: Log in sentry - current_app.logger.warn(f'Unknown contribution {contribution_id} confirmed with txid {txid}') + msg = f'Unknown contribution {contribution_id} confirmed with txid {txid}, amount {amount}' + capture_message(msg) + current_app.logger.warn(msg) return {"message": "No contribution matching id"}, 404 if contribution.status == ContributionStatus.CONFIRMED: @@ -578,8 +579,6 @@ def post_contribution_confirmation(contribution_id, to, amount, txid): 'contributor_url': make_url(f'/profile/{contribution.user.id}') if contribution.user else '', }) - # TODO: Once we have a task queuer in place, queue emails to everyone - # on funding target reached. contribution.proposal.set_funded_when_ready() diff --git a/backend/grant/rfp/models.py b/backend/grant/rfp/models.py index c9159d43..9c29743b 100644 --- a/backend/grant/rfp/models.py +++ b/backend/grant/rfp/models.py @@ -60,7 +60,7 @@ class RFP(db.Model): matching: bool = False, status: str = RFPStatus.DRAFT, ): - # TODO add status assert + assert RFPStatus.includes(status) assert Category.includes(category) self.id = gen_random_id(RFP) self.date_created = datetime.now() diff --git a/backend/grant/task/jobs.py b/backend/grant/task/jobs.py index 690c34d2..ef2e96b2 100644 --- a/backend/grant/task/jobs.py +++ b/backend/grant/task/jobs.py @@ -30,7 +30,6 @@ class ProposalReminder: assert task.job_type == 1, "Job type: {} is incorrect for ProposalReminder".format(task.job_type) from grant.proposal.models import Proposal proposal = Proposal.query.filter_by(id=task.blob["proposal_id"]).first() - # TODO - replace with email print(proposal) task.completed = True db.session.add(task) diff --git a/backend/grant/user/models.py b/backend/grant/user/models.py index 290b90ec..f16952c3 100644 --- a/backend/grant/user/models.py +++ b/backend/grant/user/models.py @@ -1,6 +1,7 @@ from flask_security import UserMixin, RoleMixin from flask_security.core import current_user from flask_security.utils import hash_password, verify_and_update_password, login_user +from sqlalchemy.ext.hybrid import hybrid_property from grant.comment.models import Comment from grant.email.models import EmailVerification, EmailRecovery from grant.email.send import send_email @@ -10,11 +11,11 @@ from grant.email.subscription_settings import ( email_subscriptions_to_dict ) from grant.extensions import ma, db, security -from grant.utils.misc import make_url, gen_random_id +from grant.utils.misc import make_url, gen_random_id, is_email from grant.utils.social import generate_social_url from grant.utils.upload import extract_avatar_filename, construct_avatar_url from grant.utils import totp_2fa -from sqlalchemy.ext.hybrid import hybrid_property +from grant.utils.exceptions import ValidationException def is_current_authed_user_id(user_id): @@ -133,8 +134,6 @@ class User(db.Model, UserMixin): backref=db.backref('users', lazy='dynamic')) arbiter_proposals = db.relationship("ProposalArbiter", lazy=True, back_populates="user") - # TODO - add create and validate methods - def __init__( self, email_address, @@ -150,6 +149,22 @@ class User(db.Model, UserMixin): self.title = title self.password = password + @staticmethod + def validate(user): + em = user.get('email_address') + if not em: + raise ValidationException('Must have email address') + if not is_email(em): + raise ValidationException('Email address looks invalid') + + t = user.get('title') + if t and len(t) > 255: + raise ValidationException('Title is too long') + + dn = user.get('display_name') + if dn and len(dn) > 255: + raise ValidationException('Display name is too long') + @staticmethod def create(email_address=None, password=None, display_name=None, title=None, _send_email=True): user = security.datastore.create_user( @@ -158,6 +173,7 @@ class User(db.Model, UserMixin): display_name=display_name, title=title ) + User.validate(vars(user)) security.datastore.commit() # user settings @@ -249,7 +265,6 @@ class User(db.Model, UserMixin): db.session.flush() def set_admin(self, is_admin: bool): - # TODO: audit entry & possibly email user self.is_admin = is_admin db.session.add(self) db.session.flush() diff --git a/backend/tests/admin/test_admin_api.py b/backend/tests/admin/test_admin_api.py index 7360a51a..67f49f81 100644 --- a/backend/tests/admin/test_admin_api.py +++ b/backend/tests/admin/test_admin_api.py @@ -245,10 +245,12 @@ class TestAdminAPI(BaseProposalCreatorConfig): def test_update_proposal(self): self.login_admin() # set to 1 (on) - resp_on = self.app.put(f"/api/v1/admin/proposals/{self.proposal.id}", data=json.dumps({"contributionMatching": 1})) + resp_on = self.app.put(f"/api/v1/admin/proposals/{self.proposal.id}", + data=json.dumps({"contributionMatching": 1})) self.assert200(resp_on) self.assertEqual(resp_on.json['contributionMatching'], 1) - resp_off = self.app.put(f"/api/v1/admin/proposals/{self.proposal.id}", data=json.dumps({"contributionMatching": 0})) + resp_off = self.app.put(f"/api/v1/admin/proposals/{self.proposal.id}", + data=json.dumps({"contributionMatching": 0})) self.assert200(resp_off) self.assertEqual(resp_off.json['contributionMatching'], 0) @@ -307,7 +309,6 @@ class TestAdminAPI(BaseProposalCreatorConfig): }) ) self.assert200(resp) - # TODO - more tests def test_create_rfp_succeeds(self): self.login_admin() @@ -315,12 +316,12 @@ class TestAdminAPI(BaseProposalCreatorConfig): resp = self.app.post( "/api/v1/admin/rfps", data=json.dumps({ - "brief": "Some brief", - "category": "CORE_DEV", - "content": "CONTENT", - "dateCloses": 1553980004, - "status": "DRAFT", - "title": "TITLE" + "brief": "Some brief", + "category": "CORE_DEV", + "content": "CONTENT", + "dateCloses": 1553980004, + "status": "DRAFT", + "title": "TITLE" }) ) self.assert200(resp) @@ -331,13 +332,12 @@ class TestAdminAPI(BaseProposalCreatorConfig): resp = self.app.post( "/api/v1/admin/rfps", data=json.dumps({ - "brief": "Some brief", - "category": "NOT_CORE_DEV", - "content": "CONTENT", - "dateCloses": 1553980004, - "status": "DRAFT", - "title": "TITLE" + "brief": "Some brief", + "category": "NOT_CORE_DEV", + "content": "CONTENT", + "dateCloses": 1553980004, + "status": "DRAFT", + "title": "TITLE" }) ) self.assert400(resp) - diff --git a/backend/tests/user/test_invite_api.py b/backend/tests/user/test_invite_api.py index 686c758e..f2927b2c 100644 --- a/backend/tests/user/test_invite_api.py +++ b/backend/tests/user/test_invite_api.py @@ -47,7 +47,10 @@ class TestUserInviteAPI(BaseProposalCreatorConfig): self.assertStatus(invites_res, 200) # Make sure we made the team, coach - self.assertTrue(len(self.other_proposal.team) == 2) # TODO: More thorough check than length + print(self.other_proposal.team) + self.assertTrue(len(self.other_proposal.team) == 2) + team_ids = [t.id for t in self.other_proposal.team] + self.assertIn(self.user.id, team_ids, 'user should be in team') def test_put_user_invite_response_reject(self): invite = ProposalTeamInvite( @@ -67,7 +70,9 @@ class TestUserInviteAPI(BaseProposalCreatorConfig): self.assertStatus(invites_res, 200) # Make sure we made the team, coach - self.assertTrue(len(self.other_proposal.team) == 1) # TODO: More thorough check than length + self.assertTrue(len(self.other_proposal.team) == 1) + team_ids = [t.id for t in self.other_proposal.team] + self.assertNotIn(self.user.id, team_ids, 'user should NOT be in team') def test_no_auth_put_user_invite_response(self): invite = ProposalTeamInvite( diff --git a/backend/tests/user/test_user_api.py b/backend/tests/user/test_user_api.py index f3cd18e7..24144ede 100644 --- a/backend/tests/user/test_user_api.py +++ b/backend/tests/user/test_user_api.py @@ -144,7 +144,7 @@ class TestUserAPI(BaseUserConfig): self.login_default_user() updated_user = animalify(copy.deepcopy(user_schema.dump(self.user))) updated_user["displayName"] = 'new display name' - updated_user["avatar"] = '' # TODO confirm avatar is no longer a dict + updated_user["avatar"] = '' updated_user["socialMedias"] = [] user_update_resp = self.app.put( diff --git a/blockchain/src/node.ts b/blockchain/src/node.ts index 4ef43be2..c7638ffb 100644 --- a/blockchain/src/node.ts +++ b/blockchain/src/node.ts @@ -33,7 +33,6 @@ export interface VOut { scriptPubKey: ScriptPubKey; } - export interface Transaction { txid: string; hex: string; @@ -46,7 +45,7 @@ export interface Transaction { time: number; vin: VIn[]; vout: VOut[]; - // TODO: fill me out, what is this? + // unclear what vjoinsplit is vjoinsplit: any[]; } @@ -74,7 +73,6 @@ export interface BlockWithTransactionIds extends Block { tx: string[]; } - export interface BlockWithTransactions extends Block { tx: Transaction[]; } @@ -107,33 +105,44 @@ export interface ValidationResponse { isvalid: boolean; } - -// TODO: Type all methods with signatures from // https://github.com/zcash/zcash/blob/master/doc/payment-api.md interface ZCashNode { getblockchaininfo: () => Promise; getblockcount: () => Promise; getblock: { - (numberOrHash: string | number, verbosity?: 1): Promise; - (numberOrHash: string | number, verbosity: 2): Promise; + (numberOrHash: string | number, verbosity?: 1): Promise< + BlockWithTransactionIds + >; + (numberOrHash: string | number, verbosity: 2): Promise< + BlockWithTransactions + >; (numberOrHash: string | number, verbosity: 0): Promise; - } + }; gettransaction: (txid: string) => Promise; validateaddress: (address: string) => Promise; z_getbalance: (address: string, minConf?: number) => Promise; - z_getnewaddress: (type?: 'sprout' | 'sapling') => Promise; + z_getnewaddress: (type?: "sprout" | "sapling") => Promise; z_listaddresses: () => Promise; - z_listreceivedbyaddress: (address: string, minConf?: number) => Promise; - z_importviewingkey: (key: string, rescan?: 'yes' | 'no' | 'whenkeyisnew', startHeight?: number) => Promise; + z_listreceivedbyaddress: ( + address: string, + minConf?: number + ) => Promise; + z_importviewingkey: ( + key: string, + rescan?: "yes" | "no" | "whenkeyisnew", + startHeight?: number + ) => Promise; z_exportviewingkey: (zaddr: string) => Promise; - z_validatepaymentdisclosure: (disclosure: string) => Promise; + z_validatepaymentdisclosure: ( + disclosure: string + ) => Promise; z_validateaddress: (address: string) => Promise; } export const rpcOptions = { url: env.ZCASH_NODE_URL, username: env.ZCASH_NODE_USERNAME, - password: env.ZCASH_NODE_PASSWORD, + password: env.ZCASH_NODE_PASSWORD }; const node: ZCashNode = stdrpc(rpcOptions); @@ -152,28 +161,31 @@ export async function initNode() { } if (info.chain.includes("test")) { network = bitcore.Networks.testnet; - } - else { + } else { network = bitcore.Networks.mainnet; } - } - catch(err) { + } catch (err) { captureException(err); log.error(err.response ? err.response.data : err); - log.error('Failed to connect to zcash node with the following credentials:\r\n', rpcOptions); + log.error( + "Failed to connect to zcash node with the following credentials:\r\n", + rpcOptions + ); process.exit(1); } // Check if sprout address is readable try { if (!env.SPROUT_ADDRESS) { - console.error('Missing SPROUT_ADDRESS environment variable, exiting'); + console.error("Missing SPROUT_ADDRESS environment variable, exiting"); process.exit(1); } await node.z_getbalance(env.SPROUT_ADDRESS as string); - } catch(err) { + } catch (err) { if (!env.SPROUT_VIEWKEY) { - log.error('Unable to view SPROUT_ADDRESS and missing SPROUT_VIEWKEY environment variable, exiting'); + log.error( + "Unable to view SPROUT_ADDRESS and missing SPROUT_VIEWKEY environment variable, exiting" + ); process.exit(1); } await node.z_importviewingkey(env.SPROUT_VIEWKEY as string); @@ -183,7 +195,7 @@ export async function initNode() { export function getNetwork() { if (!network) { - throw new Error('Called getNetwork before initNode'); + throw new Error("Called getNetwork before initNode"); } return network; } @@ -194,11 +206,15 @@ export async function getBootstrapBlockHeight(txid: string | undefined) { try { const tx = await node.gettransaction(txid); const block = await node.getblock(tx.blockhash); - const height = block.height - parseInt(env.MINIMUM_BLOCK_CONFIRMATIONS, 10); + const height = + block.height - parseInt(env.MINIMUM_BLOCK_CONFIRMATIONS, 10); return height.toString(); - } catch(err) { - console.warn(`Attempted to get block height for tx ${txid} but failed with the following error:\n`, err); - console.warn('Falling back to hard-coded starter blocks'); + } catch (err) { + console.warn( + `Attempted to get block height for tx ${txid} but failed with the following error:\n`, + err + ); + console.warn("Falling back to hard-coded starter blocks"); } } @@ -207,11 +223,10 @@ export async function getBootstrapBlockHeight(txid: string | undefined) { const net = getNetwork(); if (net === bitcore.Networks.mainnet) { return env.MAINNET_START_BLOCK; - } - else if (net === bitcore.Networks.testnet && !net.regtestEnabled) { + } else if (net === bitcore.Networks.testnet && !net.regtestEnabled) { return env.TESTNET_START_BLOCK; } // Regtest or otherwise unknown networks should start at the bottom - return '0'; + return "0"; } diff --git a/blockchain/src/util.ts b/blockchain/src/util.ts index 94438566..f15e1d7f 100755 --- a/blockchain/src/util.ts +++ b/blockchain/src/util.ts @@ -21,8 +21,7 @@ export function authenticate(secret: string) { return hash === sha256(secret); } -// TODO: Not fully confident in compatibility with most bip32 wallets, -// do more work to ensure this is reliable. +// NOTE: this is just one way to derive t-addrs export function deriveTransparentAddress(index: number, network: any) { const root = new HDPublicKey(env.BIP32_XPUB); const child = root.derive(`m/0/${index}`); @@ -39,14 +38,16 @@ export function removeItem(arr: T[], remove: T) { } export function encodeHexMemo(memo: string) { - return new Buffer(memo, 'utf8').toString('hex'); + return new Buffer(memo, "utf8").toString("hex"); } export function decodeHexMemo(memoHex: string) { - return new Buffer(memoHex, 'hex') - .toString() - // Remove null bytes from zero padding - .replace(/\0.*$/g, ''); + return ( + new Buffer(memoHex, "hex") + .toString() + // Remove null bytes from zero padding + .replace(/\0.*$/g, "") + ); } export function makeContributionMemo(contributionId: number) { @@ -54,14 +55,15 @@ export function makeContributionMemo(contributionId: number) { } export function getContributionIdFromMemo(memoHex: string) { - const matches = decodeHexMemo(memoHex).match(/Contribution ([0-9]+) on Grant\.io/); + const matches = decodeHexMemo(memoHex).match( + /Contribution ([0-9]+) on Grant\.io/ + ); if (matches && matches[1]) { return parseInt(matches[1], 10); } return false; } -// TODO: Make this more robust export function toBaseUnit(unit: number) { return Math.floor(100000000 * unit); } diff --git a/e2e/README.md b/e2e/README.md index e48d83b1..08c57b52 100644 --- a/e2e/README.md +++ b/e2e/README.md @@ -21,4 +21,4 @@ Tests can be found in `cypress/integration`. Cypress will hot-reload open tests ### CI -TODO +Coming soon. diff --git a/e2e/cypress/integration/create.fund.ms2.no-vote.re-vote.spec.ts b/e2e/cypress/integration/create.fund.ms2.no-vote.re-vote.spec.ts index c361e1c5..eb1f7a65 100644 --- a/e2e/cypress/integration/create.fund.ms2.no-vote.re-vote.spec.ts +++ b/e2e/cypress/integration/create.fund.ms2.no-vote.re-vote.spec.ts @@ -79,7 +79,6 @@ describe("create.fund.ms2.no-vote.re-vote", () => { "Request milestone payout", { timeout: 20000 } ).click(); - // TODO: fix this bug (the following fails) cy.contains(".MilestoneAction-progress-text", "voted against payout", { timeout: 20000 }); diff --git a/frontend/client/components/BasicHead.tsx b/frontend/client/components/BasicHead.tsx index 50424f19..29895da4 100644 --- a/frontend/client/components/BasicHead.tsx +++ b/frontend/client/components/BasicHead.tsx @@ -25,7 +25,6 @@ class BasicHead extends React.Component { name="keywords" content="Zcash, Zcash Foundation, Zcash Foundation Grants, Zcash Grants, Zcash Grant, ZF Grants, ZFGrants" /> - { - {/* TODO: i18n */} {/* */} {/* */} diff --git a/frontend/client/components/Comment/index.tsx b/frontend/client/components/Comment/index.tsx index 2cd80ab0..a2ada52f 100644 --- a/frontend/client/components/Comment/index.tsx +++ b/frontend/client/components/Comment/index.tsx @@ -41,7 +41,6 @@ class Comment extends React.Component { }; componentDidUpdate(prevProps: Props) { - // TODO: Come up with better check on if our comment post was a success const { isPostCommentPending, postCommentError } = this.props; if (!isPostCommentPending && !postCommentError && prevProps.isPostCommentPending) { this.setState({ reply: '', isReplying: false }); diff --git a/frontend/client/components/HeaderDetails.tsx b/frontend/client/components/HeaderDetails.tsx index a4229d59..5d0d6214 100644 --- a/frontend/client/components/HeaderDetails.tsx +++ b/frontend/client/components/HeaderDetails.tsx @@ -1,6 +1,6 @@ import React from 'react'; -// import { Helmet } from 'react-helmet'; -// import { urlToPublic } from 'utils/helpers'; +import { Helmet } from 'react-helmet'; +import { urlToPublic } from 'utils/helpers'; interface Props { title: string; @@ -12,25 +12,22 @@ interface Props { export default class HeaderDetails extends React.Component { render() { - // TODO: Uncomment once helmet is fixed - // https://github.com/nfl/react-helmet/issues/373 - return null; - // const { title, image, url, type, description } = this.props; - // return ( - // - // {`ZF Grants - ${title}`} - // {/* open graph protocol */} - // {type && } - // - // {description && } - // {url && } - // {image && } - // {/* twitter card */} - // - // {description && } - // {url && } - // {image && } - // - // ); + const { title, image, url, type, description } = this.props; + return ( + + {`ZF Grants - ${title}`} + {/* open graph protocol */} + {type && } + + {description && } + {url && } + {image && } + {/* twitter card */} + + {description && } + {url && } + {image && } + + ); } } diff --git a/frontend/client/components/Profile/index.tsx b/frontend/client/components/Profile/index.tsx index 8c9a51e0..43256ef6 100644 --- a/frontend/client/components/Profile/index.tsx +++ b/frontend/client/components/Profile/index.tsx @@ -106,7 +106,6 @@ class Profile extends React.Component { return (
- {/* TODO: customize details for funders/creators */} { return; } - // TODO: Get values from proposal const { target, funded } = this.props.proposal; const remainingTarget = target.sub(funded); const amount = parseFloat(value); diff --git a/frontend/client/components/Proposal/Comments/index.tsx b/frontend/client/components/Proposal/Comments/index.tsx index 4a7fba18..54428f6d 100644 --- a/frontend/client/components/Proposal/Comments/index.tsx +++ b/frontend/client/components/Proposal/Comments/index.tsx @@ -55,7 +55,6 @@ class ProposalComments extends React.Component { } componentDidUpdate(prevProps: Props) { - // TODO: Come up with better check on if our comment post was a success const { isPostCommentPending, postCommentError } = this.props; if (!isPostCommentPending && !postCommentError && prevProps.isPostCommentPending) { this.setState({ comment: '' }); diff --git a/frontend/client/modules/proposals/reducers.ts b/frontend/client/modules/proposals/reducers.ts index 05b2330f..08efcb67 100644 --- a/frontend/client/modules/proposals/reducers.ts +++ b/frontend/client/modules/proposals/reducers.ts @@ -386,8 +386,7 @@ export default (state = INITIAL_STATE, action: any) => { case types.PROPOSAL_UPDATES_REJECTED: return { ...state, - // TODO: Get action to send real error - updatesError: 'Failed to fetch updates', + updatesError: (payload && payload.message) || payload.toString(), isFetchingUpdates: false, }; @@ -402,8 +401,7 @@ export default (state = INITIAL_STATE, action: any) => { case types.PROPOSAL_CONTRIBUTIONS_REJECTED: return { ...state, - // TODO: Get action to send real error - fetchContributionsError: 'Failed to fetch updates', + fetchContributionsError: (payload && payload.message) || payload.toString(), isFetchingContributions: false, }; diff --git a/frontend/client/modules/rfps/reducers.ts b/frontend/client/modules/rfps/reducers.ts index 9bed264d..609cc289 100644 --- a/frontend/client/modules/rfps/reducers.ts +++ b/frontend/client/modules/rfps/reducers.ts @@ -47,8 +47,7 @@ export default (state: RFPState = INITIAL_STATE, action: any): RFPState => { case types.FETCH_RFP_REJECTED: return { ...state, - // TODO: Get action to send real error - fetchRfpsError: 'Failed to fetch rfps', + fetchRfpsError: (payload && payload.message) || payload.toString(), isFetchingRfps: false, }; case types.FETCH_RFPS_FULFILLED: diff --git a/frontend/client/utils/api.ts b/frontend/client/utils/api.ts index 1d0b3e33..98fb3fb5 100644 --- a/frontend/client/utils/api.ts +++ b/frontend/client/utils/api.ts @@ -114,7 +114,7 @@ export function formatRFPFromGet(rfp: RFP): RFP { return rfp; } -// TODO: i18n on case-by-case basis +// NOTE: i18n on case-by-case basis export function generateSlugUrl(id: number, title: string) { const slug = title .toLowerCase() diff --git a/frontend/client/utils/markdown.ts b/frontend/client/utils/markdown.ts index 8613d0c0..2feed465 100644 --- a/frontend/client/utils/markdown.ts +++ b/frontend/client/utils/markdown.ts @@ -27,7 +27,6 @@ const converters: { [key in MARKDOWN_TYPE]: Showdown.Converter } = { ...sharedOptions, noHeaderId: true, headerLevelStart: 4, - // TODO: Find a way to disable images }), }; diff --git a/frontend/package.json b/frontend/package.json index af5c11d9..3b993abe 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -60,7 +60,7 @@ "@types/react": "16.4.18", "@types/react-cropper": "^0.10.3", "@types/react-dom": "16.0.9", - "@types/react-helmet": "^5.0.7", + "@types/react-helmet": "^5.0.8", "@types/react-redux": "^6.0.2", "@types/react-router": "4.4.3", "@types/react-router-dom": "^4.3.1", @@ -126,7 +126,7 @@ "react-cropper": "^1.0.1", "react-dev-utils": "^5.0.2", "react-dom": "16.5.2", - "react-helmet": "^5.2.0", + "react-helmet": "6.0.0-beta", "react-hot-loader": "^4.3.8", "react-i18next": "^8.3.5", "react-mde": "7.0.4", diff --git a/frontend/server/components/HTML.tsx b/frontend/server/components/HTML.tsx index 416c9383..ede71704 100644 --- a/frontend/server/components/HTML.tsx +++ b/frontend/server/components/HTML.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import Helmet from 'react-helmet'; +import { Helmet } from 'react-helmet'; import { ChunkExtractor } from '@loadable/server'; export interface Props { @@ -34,13 +34,6 @@ const HTML: React.SFC = ({ - {/* TODO: import from @fortawesome */} - {/* */} {/* Custom link & meta tags from webpack */} {extractor.getLinkElements()} {linkTags.map((l, idx) => ( diff --git a/frontend/types/contribution.ts b/frontend/types/contribution.ts index e72b8a2f..4a8bbae0 100644 --- a/frontend/types/contribution.ts +++ b/frontend/types/contribution.ts @@ -13,7 +13,7 @@ export interface Contribution { export interface ContributionWithAddresses extends Contribution { addresses: { transparent: string; - // TODO: Add sapling and memo in when ready + // NOTE: Add sapling and memo in when ready // sprout: string; // memo: string; }; diff --git a/frontend/types/user.ts b/frontend/types/user.ts index 162b901a..85a122f0 100644 --- a/frontend/types/user.ts +++ b/frontend/types/user.ts @@ -2,7 +2,7 @@ import { SocialMedia } from 'types'; export interface User { userid: number; - emailAddress?: string; // TODO: Split into full user type + emailAddress?: string; emailVerified?: boolean; displayName: string; title: string; diff --git a/frontend/yarn.lock b/frontend/yarn.lock index fc0380f4..c2737825 100644 --- a/frontend/yarn.lock +++ b/frontend/yarn.lock @@ -1940,9 +1940,10 @@ "@types/node" "*" "@types/react" "*" -"@types/react-helmet@^5.0.7": - version "5.0.7" - resolved "https://registry.yarnpkg.com/@types/react-helmet/-/react-helmet-5.0.7.tgz#1cae65b2c37fe54cf56f40cd388836d4619dbc51" +"@types/react-helmet@^5.0.8": + version "5.0.8" + resolved "https://registry.yarnpkg.com/@types/react-helmet/-/react-helmet-5.0.8.tgz#f080eea6652e44f60b4574463d238f268d81d9af" + integrity sha512-ZTr12eDAYI0yUiMx1K82EHqRYa8J1BOOLus+0gL+AkksUiIPwLE0wLiXa9FNqD8r9GXAi+yRPZImkRh1JNlTkQ== dependencies: "@types/react" "*" @@ -9601,6 +9602,11 @@ react-error-overlay@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/react-error-overlay/-/react-error-overlay-4.0.1.tgz#417addb0814a90f3a7082eacba7cee588d00da89" +react-fast-compare@^2.0.2: + version "2.0.4" + resolved "https://registry.yarnpkg.com/react-fast-compare/-/react-fast-compare-2.0.4.tgz#e84b4d455b0fec113e0402c329352715196f81f9" + integrity sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw== + react-fittext@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/react-fittext/-/react-fittext-1.0.0.tgz#836a1c04f9322f6c94cb69e45c66006fc42d37a5" @@ -9617,13 +9623,14 @@ react-fuzzy@^0.5.2: fuse.js "^3.0.1" prop-types "^15.5.9" -react-helmet@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/react-helmet/-/react-helmet-5.2.0.tgz#a81811df21313a6d55c5f058c4aeba5d6f3d97a7" +react-helmet@6.0.0-beta: + version "6.0.0-beta" + resolved "https://registry.yarnpkg.com/react-helmet/-/react-helmet-6.0.0-beta.tgz#1f2ac04521951486e4fce3296d0c88aae8cabd5c" + integrity sha512-GnNWsokebTe7fe8MH2I/a2dl4THYWhthLBoMaQSRYqW5XbPo881WAJGi+lqRBjyOFryW6zpQluEkBy70zh+h9w== dependencies: - deep-equal "^1.0.1" object-assign "^4.1.1" prop-types "^15.5.4" + react-fast-compare "^2.0.2" react-side-effect "^1.1.0" react-hot-loader@^4.3.8: From a043c2dfa69bcab0a65fd919b96d56f9963db37a Mon Sep 17 00:00:00 2001 From: AMStrix Date: Wed, 13 Mar 2019 18:35:22 -0500 Subject: [PATCH 48/72] XSS and Reduced Markdown for Admin (#355) --- admin/package.json | 2 +- admin/src/components/Markdown/index.tsx | 5 ++- .../components/Moderation/ModerationItem.tsx | 2 +- admin/src/components/UserDetail/index.tsx | 6 ++- admin/src/util/md.ts | 40 ++++++++++++++++++- admin/yarn.lock | 3 +- backend/grant/email/send.py | 2 + 7 files changed, 52 insertions(+), 8 deletions(-) diff --git a/admin/package.json b/admin/package.json index 7e44e563..91d20910 100644 --- a/admin/package.json +++ b/admin/package.json @@ -106,7 +106,7 @@ "webpack-cli": "^3.1.0", "webpack-dev-server": "3.2.1", "webpack-hot-middleware": "^2.24.0", - "xss": "1.0.3" + "xss": "^1.0.3" }, "devDependencies": { "@types/bn.js": "4.11.1", diff --git a/admin/src/components/Markdown/index.tsx b/admin/src/components/Markdown/index.tsx index bd4d3b5e..97b53ab7 100644 --- a/admin/src/components/Markdown/index.tsx +++ b/admin/src/components/Markdown/index.tsx @@ -5,12 +5,13 @@ import './index.less'; interface Props extends React.HTMLAttributes { source: string; + reduced?: boolean; } export default class Markdown extends React.PureComponent { render() { - const { source, ...rest } = this.props; - const html = mdToHtml(source); + const { source, reduced, ...rest } = this.props; + const html = mdToHtml(source, reduced); // TS types seem to be fighting over react prop defs for div const divProps = rest as any; return ( diff --git a/admin/src/components/Moderation/ModerationItem.tsx b/admin/src/components/Moderation/ModerationItem.tsx index df80e311..fffd1eb5 100644 --- a/admin/src/components/Moderation/ModerationItem.tsx +++ b/admin/src/components/Moderation/ModerationItem.tsx @@ -54,7 +54,7 @@ class ModerationItem extends React.Component { } description={ - + } /> diff --git a/admin/src/components/UserDetail/index.tsx b/admin/src/components/UserDetail/index.tsx index 6fa12059..84b9158a 100644 --- a/admin/src/components/UserDetail/index.tsx +++ b/admin/src/components/UserDetail/index.tsx @@ -270,7 +270,11 @@ class UserDetailNaked extends React.Component { {' '} at {formatDateMs(c.dateCreated)}
- + } /> diff --git a/admin/src/util/md.ts b/admin/src/util/md.ts index 1ba2132b..9a508db4 100644 --- a/admin/src/util/md.ts +++ b/admin/src/util/md.ts @@ -1,4 +1,5 @@ import Showdown from 'showdown'; +import xss from 'xss'; const showdownConverter = new Showdown.Converter({ simplifiedAutoLink: true, @@ -9,6 +10,41 @@ const showdownConverter = new Showdown.Converter({ excludeTrailingPunctuationFromURLs: true, }); -export const mdToHtml = (text: string) => { - return showdownConverter.makeHtml(text); +export const mdToHtml = (text: string, reduced: boolean = false) => { + const html = showdownConverter.makeHtml(text); + return reduced ? xss(html, reducedXssOpts) : xss(html); +}; + +const reducedXssOpts = { + stripIgnoreTag: true, + whiteList: { + a: ['target', 'href', 'title'], + b: [], + blockquote: [], + br: [], + code: [], + del: [], + em: [], + h4: [], + h5: [], + h6: [], + hr: [], + i: [], + li: [], + ol: [], + p: [], + pre: [], + small: [], + sub: [], + sup: [], + strong: [], + table: ['width', 'border', 'align', 'valign'], + tbody: ['align', 'valign'], + td: ['width', 'rowspan', 'colspan', 'align', 'valign'], + tfoot: ['align', 'valign'], + th: ['width', 'rowspan', 'colspan', 'align', 'valign'], + thead: ['align', 'valign'], + tr: ['rowspan', 'align', 'valign'], + ul: [], + }, }; diff --git a/admin/yarn.lock b/admin/yarn.lock index ae35d90b..d391a490 100644 --- a/admin/yarn.lock +++ b/admin/yarn.lock @@ -8477,9 +8477,10 @@ xregexp@4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-4.0.0.tgz#e698189de49dd2a18cc5687b05e17c8e43943020" -xss@1.0.3: +xss@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/xss/-/xss-1.0.3.tgz#d04bd2558fd6c29c46113824d5e8b2a910054e23" + integrity sha512-LTpz3jXPLUphMMmyufoZRSKnqMj41OVypZ8uYGzvjkMV9C1EdACrhQl/EM8Qfh5htSAuMIQFOejmKAZGkJfaCg== dependencies: commander "^2.9.0" cssfilter "0.0.10" diff --git a/backend/grant/email/send.py b/backend/grant/email/send.py index 4598a9cc..1758509d 100644 --- a/backend/grant/email/send.py +++ b/backend/grant/email/send.py @@ -385,6 +385,8 @@ def make_envelope(to, type, email_args): def sendgrid_send(mail): + to = mail.___to + type = mail.___type try: sg = sendgrid.SendGridAPIClient(apikey=SENDGRID_API_KEY) res = sg.client.mail.send.post(request_body=mail.get()) From e018f11018ff54eb576fb3238c14f47a1e2d097d Mon Sep 17 00:00:00 2001 From: AMStrix Date: Wed, 13 Mar 2019 18:36:06 -0500 Subject: [PATCH 49/72] Zcash e2e basics (#329) * e2e: remove more eth related stuff, update cypress * BE: e2e endpoints and interceptions for emails and blockchain reqs * e2e: expand browse test for proposals and comments * e2e: remove old tests, add new ones * e2e: intercept blockchain_get /contribution/addresses * be: disable sentry for e2e, add DEBUG to e2e endpoint register conditional * post-merge adjustments * fix merge related bug * post merge issue --- backend/grant/app.py | 13 +- backend/grant/e2e/__init__.py | 1 + backend/grant/e2e/views.py | 150 + backend/grant/email/send.py | 23 +- backend/grant/settings.py | 4 +- backend/grant/utils/requests.py | 20 +- e2e/cypress/fixtures/example.json | 5 + e2e/cypress/helpers.ts | 296 +- e2e/cypress/integration/auth.recover.ts | 46 + e2e/cypress/integration/auth.sign-in.spec.ts | 51 + e2e/cypress/integration/authenticate.spec.ts | 14 - e2e/cypress/integration/browse.spec.ts | 87 +- e2e/cypress/integration/create.cancel.spec.ts | 47 - e2e/cypress/integration/create.flow.spec.ts | 173 - .../integration/create.fund.cancel.spec.ts | 61 - ...te.fund.complete.minority-no-votes.spec.ts | 126 - .../integration/create.fund.complete.spec.ts | 103 - .../create.fund.ms2.majority-no-vote.spec.ts | 60 - .../create.fund.ms2.no-vote.re-vote.spec.ts | 86 - ...reate.fund.ms2.refund-after-payout.spec.ts | 69 - .../create.fund.ms2.revert-no-vote.spec.ts | 93 - e2e/cypress/integration/proposal.fund.spec.ts | 77 + e2e/cypress/integration/sandbox.ts | 15 - e2e/cypress/support/index.js | 7 +- e2e/package.json | 7 +- e2e/yarn.lock | 4326 +++++++---------- 26 files changed, 2250 insertions(+), 3710 deletions(-) create mode 100644 backend/grant/e2e/__init__.py create mode 100644 backend/grant/e2e/views.py create mode 100644 e2e/cypress/fixtures/example.json create mode 100644 e2e/cypress/integration/auth.recover.ts create mode 100644 e2e/cypress/integration/auth.sign-in.spec.ts delete mode 100644 e2e/cypress/integration/authenticate.spec.ts delete mode 100644 e2e/cypress/integration/create.cancel.spec.ts delete mode 100644 e2e/cypress/integration/create.flow.spec.ts delete mode 100644 e2e/cypress/integration/create.fund.cancel.spec.ts delete mode 100644 e2e/cypress/integration/create.fund.complete.minority-no-votes.spec.ts delete mode 100644 e2e/cypress/integration/create.fund.complete.spec.ts delete mode 100644 e2e/cypress/integration/create.fund.ms2.majority-no-vote.spec.ts delete mode 100644 e2e/cypress/integration/create.fund.ms2.no-vote.re-vote.spec.ts delete mode 100644 e2e/cypress/integration/create.fund.ms2.refund-after-payout.spec.ts delete mode 100644 e2e/cypress/integration/create.fund.ms2.revert-no-vote.spec.ts create mode 100644 e2e/cypress/integration/proposal.fund.spec.ts delete mode 100644 e2e/cypress/integration/sandbox.ts diff --git a/backend/grant/app.py b/backend/grant/app.py index 90c02a7d..65c2b094 100644 --- a/backend/grant/app.py +++ b/backend/grant/app.py @@ -10,10 +10,9 @@ from flask_security import SQLAlchemyUserDatastore from flask_sslify import SSLify from sentry_sdk.integrations.flask import FlaskIntegration from sentry_sdk.integrations.logging import LoggingIntegration - -from grant import commands, proposal, user, comment, milestone, admin, email, blockchain, task, rfp +from grant import commands, proposal, user, comment, milestone, admin, email, blockchain, task, rfp, e2e from grant.extensions import bcrypt, migrate, db, ma, security, limiter -from grant.settings import SENTRY_RELEASE, ENV +from grant.settings import SENTRY_RELEASE, ENV, E2E_TESTING, DEBUG from grant.utils.auth import AuthException, handle_auth_error, get_authed_user from grant.utils.exceptions import ValidationException @@ -52,19 +51,16 @@ def create_app(config_objects=["grant.settings"]): else: return jsonify({"message": error_message}), err.code - @app.errorhandler(404) def handle_notfound_error(err): error_message = "Unknown route '{} {}'".format(request.method, request.path) return jsonify({"message": error_message}), 404 - @app.errorhandler(429) def handle_limit_error(err): app.logger.warn(f'Rate limited request to {request.method} {request.path} from ip {request.remote_addr}') return jsonify({"message": "You’ve done that too many times, please wait and try again later"}), 429 - @app.errorhandler(Exception) def handle_exception(err): sentry_sdk.capture_exception(err) @@ -80,7 +76,7 @@ def create_app(config_objects=["grant.settings"]): register_shellcontext(app) register_commands(app) - if not app.config.get("TESTING"): + if not (app.config.get("TESTING") or E2E_TESTING): sentry_logging = LoggingIntegration( level=logging.INFO, event_level=logging.ERROR @@ -130,6 +126,9 @@ def register_blueprints(app): app.register_blueprint(blockchain.views.blueprint) app.register_blueprint(task.views.blueprint) app.register_blueprint(rfp.views.blueprint) + if E2E_TESTING and DEBUG: + print('Warning: e2e end-points are open, this should only be the case for development or testing') + app.register_blueprint(e2e.views.blueprint) def register_shellcontext(app): diff --git a/backend/grant/e2e/__init__.py b/backend/grant/e2e/__init__.py new file mode 100644 index 00000000..14cd5bd9 --- /dev/null +++ b/backend/grant/e2e/__init__.py @@ -0,0 +1 @@ +from . import views diff --git a/backend/grant/e2e/views.py b/backend/grant/e2e/views.py new file mode 100644 index 00000000..6cb5db44 --- /dev/null +++ b/backend/grant/e2e/views.py @@ -0,0 +1,150 @@ +from datetime import datetime +from random import randint +from math import floor + +from flask import Blueprint + +from grant.comment.models import Comment +from grant.extensions import db +from grant.milestone.models import Milestone +from grant.proposal.models import ( + Proposal, + ProposalArbiter, + ProposalContribution, +) +from grant.user.models import User, admin_user_schema + +from grant.utils.enums import ( + ProposalStatus, + ProposalStage, + Category, + ContributionStatus, +) + +last_email = None + + +def create_proposals(count, category, stage, with_comments=False): + user = User.query.filter_by().first() + for i in range(count): + p = Proposal.create( + stage=stage, + status=ProposalStatus.LIVE, + title=f'Fake Proposal #{i} {category} {stage}', + content=f'My fake proposal content, numero {i}', + brief=f'This is proposal {i} generated by e2e testing', + category=category, + target="123.456", + payout_address="fake123", + deadline_duration=100 + ) + p.date_published = datetime.now() + p.team.append(user) + db.session.add(p) + db.session.flush() + num_ms = randint(1, 9) + for j in range(num_ms): + m = Milestone( + title=f'Fake MS {j}', + content=f'Fake milestone #{j} on fake proposal #{i} {category} {stage}!', + date_estimated=datetime.now(), + payout_percent=str(floor(1 / num_ms * 100)), + immediate_payout=j == 0, + proposal_id=p.id, + index=j + ) + db.session.add(m) + # limit comment creation as it is slow + if i == 0 and with_comments: + for j in range(31): + c = Comment( + proposal_id=p.id, + user_id=user.id, + parent_comment_id=None, + content=f'Fake comment #{j} on fake proposal #{i} {category} {stage}!' + ) + db.session.add(c) + if stage == ProposalStage.FUNDING_REQUIRED: + stake = p.create_contribution('1', None, True) + stake.confirm('fakestaketxid', '1') + db.session.add(stake) + db.session.flush() + fund = p.create_contribution('100', None, False) + fund.confirm('fakefundtxid0', '100') + db.session.add(fund) + db.session.flush() + p.status = ProposalStatus.LIVE + db.session.add(p) + db.session.flush() + + # db.session.flush() + + +def create_user(key: str): + pw = f"e2epassword{key}" + user = User.create( + email_address=f"{key}@testing.e2e", + password=pw, + display_name=f"{key} Endtoenderson", + title=f"title{key}", + ) + user.email_verification.has_verified = True + db.session.add(user) + db.session.flush() + dump = admin_user_schema.dump(user) + dump['password'] = pw + return dump + + +blueprint = Blueprint('e2e', __name__, url_prefix='/api/v1/e2e') + + +@blueprint.route("/setup", methods=["GET"]) +def setup(): + db.session.commit() # important, otherwise drop_all hangs + db.drop_all() + db.session.commit() + db.create_all() + db.session.commit() + default_user = create_user('default') + other_user = create_user('other') + create_proposals(12, Category.COMMUNITY, ProposalStage.FUNDING_REQUIRED, True) + create_proposals(13, Category.CORE_DEV, ProposalStage.WIP, False) + create_proposals(15, Category.DOCUMENTATION, ProposalStage.COMPLETED) + create_proposals(5, Category.ACCESSIBILITY, ProposalStage.FAILED) + db.session.commit() + return { + 'default_user': default_user, + 'other_user': other_user, + 'proposalCounts': { + 'categories': [ + {"key": Category.COMMUNITY, "count": 12}, + {"key": Category.CORE_DEV, "count": 13}, + {"key": Category.DOCUMENTATION, "count": 15}, + {"key": Category.ACCESSIBILITY, "count": 5}, + ], + 'stages': [ + {"key": ProposalStage.FUNDING_REQUIRED, "count": 12}, + {"key": ProposalStage.WIP, "count": 13}, + {"key": ProposalStage.COMPLETED, "count": 15}, + {"key": ProposalStage.FAILED, "count": 5}, + ] + } + } + + +@blueprint.route("/email", methods=["GET"]) +def get_email(): + return last_email + + +@blueprint.route("/contribution/confirm", methods=["GET"]) +def confirm_contributions(): + contributions = ProposalContribution.query \ + .filter(ProposalContribution.status == ContributionStatus.PENDING).all() + for c in contributions: + c.confirm('fakefundedtxid1', '23.456') + db.session.add(c) + c.proposal.set_funded_when_ready() + db.session.commit() + return {} diff --git a/backend/grant/email/send.py b/backend/grant/email/send.py index 1758509d..dcc4d737 100644 --- a/backend/grant/email/send.py +++ b/backend/grant/email/send.py @@ -1,13 +1,14 @@ +from .subscription_settings import EmailSubscription, is_subscribed +from sendgrid.helpers.mail import Email, Mail, Content +from python_http_client import HTTPError +from grant.utils.misc import make_url +from sentry_sdk import capture_exception +from grant.settings import SENDGRID_API_KEY, SENDGRID_DEFAULT_FROM, SENDGRID_DEFAULT_FROMNAME, UI +from grant.settings import SENDGRID_API_KEY, SENDGRID_DEFAULT_FROM, UI, E2E_TESTING import sendgrid from threading import Thread from flask import render_template, Markup, current_app -from grant.settings import SENDGRID_API_KEY, SENDGRID_DEFAULT_FROM, SENDGRID_DEFAULT_FROMNAME, UI -from sentry_sdk import capture_exception -from grant.utils.misc import make_url -from python_http_client import HTTPError -from sendgrid.helpers.mail import Email, Mail, Content -from .subscription_settings import EmailSubscription, is_subscribed default_template_args = { 'home_url': make_url('/'), @@ -389,8 +390,14 @@ def sendgrid_send(mail): type = mail.___type try: sg = sendgrid.SendGridAPIClient(apikey=SENDGRID_API_KEY) - res = sg.client.mail.send.post(request_body=mail.get()) - current_app.logger.info('Just sent an email to %s of type %s, response code: %s' % (to, type, res.status_code)) + if E2E_TESTING: + from grant.e2e import views + views.last_email = mail.get() + current_app.logger.info(f'Just set last_email for e2e to pickup, to: {to}, type: {type}') + else: + res = sg.client.mail.send.post(request_body=mail.get()) + current_app.logger.info('Just sent an email to %s of type %s, response code: %s' % + (to, type, res.status_code)) except HTTPError as e: current_app.logger.info('An HTTP error occured while sending an email to %s - %s: %s' % (to, e.__class__.__name__, e)) diff --git a/backend/grant/settings.py b/backend/grant/settings.py index 9f99d3fb..2cdcc2ae 100644 --- a/backend/grant/settings.py +++ b/backend/grant/settings.py @@ -15,7 +15,9 @@ env.read_env() ENV = env.str("FLASK_ENV", default="production") DEBUG = ENV == "development" SITE_URL = env.str('SITE_URL', default='https://zfnd.org') -SQLALCHEMY_DATABASE_URI = env.str("DATABASE_URL") +E2E_TESTING = env.str("E2E_TESTING", default=None) +E2E_DATABASE_URL = env.str("E2E_DATABASE_URL", default=None) +SQLALCHEMY_DATABASE_URI = E2E_DATABASE_URL if E2E_TESTING else env.str("DATABASE_URL") SQLALCHEMY_ECHO = False # True will print queries to log QUEUES = ["default"] SECRET_KEY = env.str("SECRET_KEY") diff --git a/backend/grant/utils/requests.py b/backend/grant/utils/requests.py index 2994ae52..9b18c80e 100644 --- a/backend/grant/utils/requests.py +++ b/backend/grant/utils/requests.py @@ -1,5 +1,5 @@ import requests -from grant.settings import BLOCKCHAIN_REST_API_URL, BLOCKCHAIN_API_SECRET +from grant.settings import BLOCKCHAIN_REST_API_URL, BLOCKCHAIN_API_SECRET, E2E_TESTING ### REST API ### @@ -12,6 +12,8 @@ def handle_res(res): def blockchain_get(path, params=None): + if E2E_TESTING: + return blockchain_rest_e2e(path, params) res = requests.get( f'{BLOCKCHAIN_REST_API_URL}{path}', headers={'authorization': BLOCKCHAIN_API_SECRET}, @@ -21,9 +23,25 @@ def blockchain_get(path, params=None): def blockchain_post(path, data=None): + if E2E_TESTING: + return blockchain_rest_e2e(path, data) res = requests.post( f'{BLOCKCHAIN_REST_API_URL}{path}', headers={'authorization': BLOCKCHAIN_API_SECRET}, json=data, ) return handle_res(res) + + +def blockchain_rest_e2e(path, data): + if '/bootstrap' in path: + return { + 'startHeight': 123, + 'currentHeight': 456, + } + if '/contribution/addresses' in path: + return { + 'transparent': 't123', + } + + raise Exception(f'blockchain_post_e2e does not recognize path: {path}') diff --git a/e2e/cypress/fixtures/example.json b/e2e/cypress/fixtures/example.json new file mode 100644 index 00000000..da18d935 --- /dev/null +++ b/e2e/cypress/fixtures/example.json @@ -0,0 +1,5 @@ +{ + "name": "Using fixtures to represent data", + "email": "hello@cypress.io", + "body": "Fixtures are a great way to mock data for responses to routes" +} \ No newline at end of file diff --git a/e2e/cypress/helpers.ts b/e2e/cypress/helpers.ts index 607aa11a..45fbf10a 100644 --- a/e2e/cypress/helpers.ts +++ b/e2e/cypress/helpers.ts @@ -1,292 +1,22 @@ -import Web3 = require("web3"); -import * as CrowdFundFactory from "../../contract/build/contracts/CrowdFundFactory.json"; -import { WebsocketProvider } from "web3/providers"; -import * as sigUtil from "eth-sig-util"; - -const WEB3_PROVIDER = `ws://localhost:8545`; - -// accounts generated using `ganache -s testGrantIo` -export const testAccounts = [ - [ - "0x0a4ab0753416ce1bebef3855fd081c170ae0194f", - "872017489e13bc3d7fec343d14691ac3c95a7904651113ce018a6ee21ae70a6e" - ], - [ - "0x926604d5a2383eae9d88eb4e884b1a34b3546194", - "492e6984a9faa04e5aad31219db4db9c927ed011b2b56b3068b1ff2b34e43f00" - ], - [ - "0xf277b4a0b9c89c07cc331b38d87a5a382501ed1a", - "c6aba74cc839af98def2819a85949847f80af42d11fefab4ecb713752261099a" - ], - [ - "0x6e6324d0927fb0aee2cbb1c915bcdc47c4f45a37", - "c66b30565895ef84c9e1cda943d828f9a91e6a0c0624caae76a661e35c2dc722" - ], - [ - "0xa696d8f7cfd0136c971492d0e9312c139cb18173", - "5f33d8df218aaf0b2bfa640d04f69aa234a1997fa08b4db606e6b022aa18cc8c" - ], - [ - "0xf90685b1a48259e192876744ff7829c1ba995093", - "9d1e415439a8411e354e72acd944cae82c3c4296c5bef6ef5d848a97a94f9ea8" - ], - [ - "0x9a4e6001044e16c5fe1c98f1ee71ce0b0005ad9b", - "cb9c060f86cec728b11a6d1143de09d221bc3a417ea9ac3e3b2922589c259ed5" - ], - [ - "0x3d03d67dbb26d13fafeebba55f5b6346213f602a", - "68e1c39abdcc8fc7d801237eb730b8e0f3199e1841209944f8c63580749a6f61" - ], - [ - "0x78ae7d98a2291093a4ff9e5003de4ab0d2a82169", - "f4c9cf60f3f91559af004f7a8de8dfebe3121a2ac418ea4f00ec34b5c841ed42" - ], - [ - "0x3cdbcc74770a13cba2045da4740c4c91ddd99b9e", - "d5c02d0db53291c4074a249e983a2f117d3ebd4155d606edde51a9be7e5deee6" - ] -]; - -// keep our own web3 to avoid messing with App's Provider -let e2eWeb3: Web3; -let appWeb3: Web3; -// open(method: string, url: string, async: boolean, username?: string | null, password?: string | null): void; -export const loadWeb3 = (swapAcctIndex: number) => (window: any) => { - if (appWeb3) { - ((appWeb3.currentProvider as WebsocketProvider) - .connection as WebSocket).close(); - } - if (e2eWeb3) { - ((e2eWeb3.currentProvider as WebsocketProvider) - .connection as WebSocket).close(); - } - appWeb3 = new Web3(WEB3_PROVIDER); - e2eWeb3 = new Web3(WEB3_PROVIDER); - - // modify backend proposal fields derived using server time - modifyProposalsApi(window); - - // window.web3.provider gets passed into new Web3 by frontend - // we will inject this for setting contract.options.gas in frontend - // this solves gas exhaustion issues - (appWeb3.currentProvider as any)._e2eContractGas = 5000000; - - // intercept eth_signTypedData_v3 calls and sign (ganache doesn't support yet) - // intercept eth_accounts RPC calls and fiddle with account[0] - const origSend = appWeb3.currentProvider.send; - const newSend = makeSendSpy(origSend, swapAcctIndex); - appWeb3.currentProvider.send = newSend; - window.web3 = appWeb3; -}; - -// intercept and modify/replace json rpc calls -const makeSendSpy = (origSend: any, swapAcctIndex: number) => { - return function(opts: any, cb: any) { - let interceptedCb = cb; - if (opts.method === "eth_signTypedData_v3") { - const [acct, data] = opts.params; - console.log(`e2e: eth_signTypedData_v3 signing for ${acct}`); - const rawTypedData = JSON.parse(data); - const signedMessage = signTypedData(acct, rawTypedData); - interceptedCb(null, { result: signedMessage }); - } else { - if (opts.method === "eth_accounts") { - interceptedCb = (err: any, res: any) => { - console.log( - `e2e: swapping account[0] with account[${swapAcctIndex}] (${ - res.result[swapAcctIndex] - })` - ); - const acctZero = res.result[0]; - res.result[0] = res.result[swapAcctIndex]; - res.result[swapAcctIndex] = acctZero; - cb(err, res); - }; - } - origSend.bind(appWeb3.currentProvider)(opts, interceptedCb); - } - }; -}; - -// sign data using ganache determined private keys + eth-sig-util -const signTypedData = (account: string, typedData: any) => { - const testAccount = testAccounts.find(ta => ta[0] === account.toLowerCase()); - if (testAccount) { - const privateKey = new Buffer(testAccount[1], "hex"); - const sig = sigUtil.signTypedData(privateKey, { data: typedData }); - return sig; - } else { - throw new Error( - `e2e helpers.sign: Could not find privateKey for account ${account}` - ); - } -}; - -// cypress doesn't yet support modifying incoming xhr requests -// https://github.com/cypress-io/cypress/tree/full-network-stubbing-687 -const modifyProposalsApi = (window: any) => { - patchXMLHttpRequest(window, /proposals\/([0-9]+)?/, text => { - const json = JSON.parse(text); - const mod = (proposal: any) => { - if (proposal.crowdFund) { - proposal.crowdFund.milestones.forEach((ms: any) => { - if (ms.state === "ACTIVE") { - // ms.state based on server time, let's use browser time - ms.state = - (ms.payoutRequestVoteDeadline > window.Date.now() && "ACTIVE") || - (ms.percentAgainstPayout >= 50 && "REJECTED") || - "PAID"; - if (ms.state != "ACTIVE") { - console.log( - `e2e modifyProposalApi changed ms.state to ${ms.state}` - ); - } - } - return ms; - }); - } - return proposal; - }; - if (Array.isArray(json)) { - json.forEach(mod); - } else { - mod(json); - } - return JSON.stringify(json); - }); -}; - -// patch the window's XMLHttpRequest with modify fn -const patchXMLHttpRequest = ( - window: any, - urlMatch: RegExp, - modify: (text: string) => string -) => { - const script = window.document.createElement("script"); - script.text = xhookText; - window.__afterxhook__ = function() { - console.log("xhook.after declaration"); - window.xhook.after(function(request: any, response: any) { - if (urlMatch.test(request.url)) response.text = modify(response.text); - }); - }; - window.document.head.appendChild(script); -}; - -const mineBlock = () => - e2eWeb3.currentProvider.send( - { - jsonrpc: "2.0", - method: "evm_mine", - params: [], - id: Date.now() - }, - () => null - ); - -const evmIncreaseTime = (seconds: number) => - new Promise((res, rej) => { - e2eWeb3.currentProvider.send( - { - jsonrpc: "2.0", - method: "evm_increaseTime", - params: [seconds], - id: Date.now() - }, - (err, _) => (err && rej(err)) || res() - ); - }); - export const increaseTime = (cy: Cypress.Chainable, ms: number) => { console.log("increasetime", ms); cy.log("INCREASE TIME", ms + "ms"); cy.window({ log: false }) - .then(w => evmIncreaseTime(Math.round(ms / 1000))) + // .then(w => evmIncreaseTime(Math.round(ms / 1000))) .then(() => syncTimeWithEvm(cy)); }; export const syncTimeWithEvm = (cy: Cypress.Chainable) => { - cy.window({ log: false }) - .then(w => { - mineBlock(); - return e2eWeb3.eth - .getBlock("latest") - .then((x: any) => x.timestamp * 1000); - }) - .then(t => { - cy.log("SYNC TIME WITH EVM", new Date(t).toString()); - cy.clock({ log: false }).then(x => x.restore()); // important for repeated calls! - cy.clock(t, ["Date"] as any, { log: false }); - }); + // cy.window({ log: false }) + // .then(w => { + // mineBlock(); + // return e2eWeb3.eth + // .getBlock("latest") + // .then((x: any) => x.timestamp * 1000); + // }) + // .then(t => { + // cy.log("SYNC TIME WITH EVM", new Date(t).toString()); + // cy.clock({ log: false }).then(x => x.restore()); // important for repeated calls! + // cy.clock(t, ["Date"] as any, { log: false }); + // }); }; - -export const createCrowdFund = (web3: Web3) => { - const HOUR = 3600; - const DAY = HOUR * 24; - const ETHER = 10 ** 18; - const NOW = Math.round(new Date().getTime() / 1000); - - return web3.eth.net.getId().then((id: number) => { - const factoryAddy = (CrowdFundFactory as any).networks[id].address; - const factory = new web3.eth.Contract( - (CrowdFundFactory as any).abi, - factoryAddy - ); - - return web3.eth.getAccounts().then((accounts: string[]) => { - const raiseGoal = 1 * ETHER; - const beneficiary = accounts[1]; - const trustees = [accounts[1]]; - const milestones = [raiseGoal + ""]; - const deadline = NOW + DAY * 100; - const milestoneVotingPeriod = HOUR; - const immediateFirstMilestonePayout = false; - - return factory.methods - .createCrowdFund( - raiseGoal + "", - beneficiary, - trustees, - milestones, - deadline, - milestoneVotingPeriod, - immediateFirstMilestonePayout - ) - .send({ - from: accounts[4] - // important - // gas: 3695268 - }) - .then( - (receipt: any) => - receipt.events.ContractCreated.returnValues.newAddress - ); - }); - }); -}; - -export const randomString = () => { - return Math.random() - .toString(36) - .substring(7); -}; - -export const randomHex = function(len: number) { - const maxlen = 8; - const min = Math.pow(16, Math.min(len, maxlen) - 1); - const max = Math.pow(16, Math.min(len, maxlen)) - 1; - const n = Math.floor(Math.random() * (max - min + 1)) + min; - let r = n.toString(16); - while (r.length < len) { - r = r + randomHex(len - maxlen); - } - return r; -}; - -// XHook - v1.4.9 - https://github.com/jpillora/xhook -// Jaime Pillora - MIT Copyright 2018 -const xhookText = - '(function(a,b){var c,d,e,f,g,h,i,j,k,l,m,n,o,p,q,r,s,t,u,v,w,x,y,z,A,B,C,D,E,F,G,H=[].indexOf||function(a){for(var b=0,c=this.length;b=0||(f=f===b?d[a].length:f,d[a].splice(f,0,c))},c[m]=function(a,c){var f;if(a===b)return void(d={});c===b&&(d[a]=[]),f=e(a).indexOf(c),f!==-1&&e(a).splice(f,1)},c[h]=function(){var b,d,f,g,h,i,j,k;for(b=D(arguments),d=b.shift(),a||(b[0]=z(b[0],y(d))),g=c["on"+d],g&&g.apply(c,b),k=e(d).concat(e("*")),f=i=0,j=k.length;i2)throw"invalid hook";return F[n](d,a,b)},F[c]=function(a,b){if(a.length<2||a.length>3)throw"invalid hook";return F[n](c,a,b)},F.enable=function(){q[u]=t,"function"==typeof r&&(q[g]=r),k&&(q[i]=s)},F.disable=function(){q[u]=F[u],q[g]=F[g],k&&(q[i]=k)},v=F.headers=function(a,b){var c,d,e,f,g,h,i,j,k;switch(null==b&&(b={}),typeof a){case"object":d=[];for(e in a)g=a[e],f=e.toLowerCase(),d.push(f+":\\t"+g);return d.join("\\n")+"\\n";case"string":for(d=a.split("\\n"),i=0,j=d.length;ib&&b<4;)k[o]=++b,1===b&&k[h]("loadstart",{}),2===b&&G(),4===b&&(G(),E()),k[h]("readystatechange",{}),4===b&&(t.async===!1?g():setTimeout(g,0))},g=function(){l||k[h]("load",{}),k[h]("loadend",{}),l&&(k[o]=0)},b=0,x=function(a){var b,d;if(4!==a)return void i(a);b=F.listeners(c),(d=function(){var a;return b.length?(a=b.shift(),2===a.length?(a(t,w),d()):3===a.length&&t.async?a(t,w,d):d()):i(4)})()},k=t.xhr=f(),I.onreadystatechange=function(a){try{2===I[o]&&r()}catch(a){}4===I[o]&&(D=!1,r(),q()),x(I[o])},m=function(){l=!0},k[n]("error",m),k[n]("timeout",m),k[n]("abort",m),k[n]("progress",function(){b<3?x(3):k[h]("readystatechange",{})}),("withCredentials"in I||F.addWithCredentials)&&(k.withCredentials=!1),k.status=0,L=e.concat(p);for(J=0,K=L.length;J + +describe("auth.recover", () => { + let stubs: any; + before(() => { + cy.request("http://localhost:5000/api/v1/e2e/setup").then( + r => (stubs = r.body) + ); + }); + + it("should be able to recover account", () => { + cy.visit("/auth/sign-in"); + cy.title().should("include", "ZF Grants - Sign in"); + cy.get("html").then(el => (el[0].style.scrollBehavior = "auto")); + + cy.contains("a", "Recover your acc").click(); + cy.get("input[placeholder='email address']").type( + stubs.defaultUser.emailAddress + ); + cy.contains("button", "Send").click(); + cy.contains("Please check your email "); + + cy.request("http://localhost:5000/api/v1/e2e/email").then(r => { + const txt = r.body.content.find((x: any) => x.type === "text/plain") + .value; + const url = txt.match(/http.*\/email\/recover.*/)[0]; + cy.visit(url); + }); + cy.contains("Reset"); + cy.get("input[name='password']").type("muhnewpassword"); + cy.get("input[name='passwordConfirm']").type("muhnewpassword"); + cy.contains("Button", "Change pass").click(); + cy.contains("Password has been reset"); + cy.contains("button", "Sign in").click(); + + cy.get("input[placeholder='email']") + .clear() + .type(stubs.defaultUser.emailAddress); + cy.get("input[placeholder='password']") + .clear() + .type("muhnewpassword"); + cy.contains("button", "Sign in").click(); + cy.contains("default Endtoenderson"); + cy.contains("button", "Edit profile"); + }); +}); diff --git a/e2e/cypress/integration/auth.sign-in.spec.ts b/e2e/cypress/integration/auth.sign-in.spec.ts new file mode 100644 index 00000000..fddef272 --- /dev/null +++ b/e2e/cypress/integration/auth.sign-in.spec.ts @@ -0,0 +1,51 @@ +/// + +describe("auth.sign-in", () => { + let stubs: any; + before(() => { + cy.request("http://localhost:5000/api/v1/e2e/setup").then( + r => (stubs = r.body) + ); + }); + it("should be able to login", () => { + cy.visit("/"); + cy.title().should("include", "ZF Grants - Home"); + cy.get("html").then(el => (el[0].style.scrollBehavior = "auto")); + + cy.contains("a", "Sign in").then(x => console.log(stubs)); + cy.contains("a", "Sign in").click(); + + cy.get("input[placeholder='email']").type(stubs.defaultUser.emailAddress); + cy.get("input[placeholder='password']").type( + stubs.defaultUser.password + "bad" + ); + cy.contains("button", "Sign in").click(); + cy.contains("Invalid passw"); + + cy.get("input[placeholder='email']") + .clear() + .type("notin@thesystem.com"); + cy.get("input[placeholder='password']") + .clear() + .type(stubs.defaultUser.password); + cy.contains("button", "Sign in").click(); + cy.contains("No user exists"); + + cy.get("input[placeholder='email']") + .clear() + .type(stubs.defaultUser.emailAddress); + cy.get("input[placeholder='password']") + .clear() + .type(stubs.defaultUser.password); + cy.contains("button", "Sign in").click(); + + cy.contains("default Endtoenderson"); + cy.contains("button", "Edit profile"); + + cy.get(".AuthButton").click(); + cy.contains("a", "Settings").click(); + cy.contains("Account"); + cy.contains("Notifications"); + cy.contains("Change Password"); + }); +}); diff --git a/e2e/cypress/integration/authenticate.spec.ts b/e2e/cypress/integration/authenticate.spec.ts deleted file mode 100644 index 03a71304..00000000 --- a/e2e/cypress/integration/authenticate.spec.ts +++ /dev/null @@ -1,14 +0,0 @@ -/// -import { - loadWeb3, - increaseTime, - syncTimeWithEvm, - randomString -} from "../helpers"; -import { createDemoProposal, authenticateUser } from "../parts"; - -describe("authenticate", () => { - it("authenticates and creates if necessary", () => { - authenticateUser(cy, 0); - }); -}); diff --git a/e2e/cypress/integration/browse.spec.ts b/e2e/cypress/integration/browse.spec.ts index ab3c5a94..1ed6fc3c 100644 --- a/e2e/cypress/integration/browse.spec.ts +++ b/e2e/cypress/integration/browse.spec.ts @@ -1,47 +1,66 @@ /// -import { loadWeb3, randomString, randomHex } from "../helpers"; describe("browse", () => { + let stubs; + before(() => { + cy.request("http://localhost:5000/api/v1/e2e/setup").then( + r => (stubs = r.body) + ); + }); it("should load and be able to browse pages", () => { - cy.visit("http://localhost:3000", { onBeforeLoad: loadWeb3(0) }); - cy.title().should("include", "Grant.io - Home"); + // cy.visit("http://localhost:3000"); + cy.visit("/"); + cy.title().should("include", "ZF Grants - Home"); + cy.get("html").then(el => (el[0].style.scrollBehavior = "auto")); - // test hero create link - cy.get('.Home-hero-buttons a[href="/create"]') - // {force: true} here overcomes a strange issue where the button moves up under the header - // this is likely a cypress scroll related problem - .click({ force: true }); - cy.title().should("include", "Grant.io - Create a Proposal"); + cy.contains("a", "Proposals").click(); - // browse back home - cy.get('.Header a[href="/"]').click(); + cy.contains(".ant-select", "Newest").click(); + cy.wait(100); + cy.contains(".ant-select-dropdown", "Oldest").click(); - // test hero explore link - cy.get('.Home-hero-buttons a[href="/proposals"]') - // {force: true} here overcomes a strange issue where the button moves up under the header - // this is likely a cypress-related problem - .click({ force: true }); - cy.title().should("include", "Grant.io - Browse proposals"); + cy.contains( + ".ProposalCard", + "Fake Proposal #0 COMMUNITY FUNDING_REQ" + ).click(); + cy.contains("h1", "Fake Proposal #0 COMMUNITY FUNDING_REQ"); + cy.contains(".ant-tabs-tab", "Discussion").click(); + cy.contains("Fake comment #30"); + cy.contains("Fake comment #21"); + cy.get(".ProposalComments").should("not.contain", "Fake comment #20"); + cy.contains("button", "Older Comments").click(); + cy.contains("Fake comment #11"); + cy.get(".ProposalComments").should("not.contain", "Fake comment #10"); - // browse back home - cy.get('.Header a[href="/"]').click(); + cy.contains("a", "Proposals").click(); + cy.contains(".ant-radio-wrapper", "All").click(); // FILTER + cy.contains(".ProposalCard", "Fake Proposal #0 COMMUNITY FUNDING_REQUIRED"); + cy.contains(".ant-pagination-item", "5"); + cy.contains(".ant-radio-wrapper", "In prog").click(); // FILTER + cy.contains(".ProposalCard", "Fake Proposal #0 CORE_DEV WIP"); + cy.contains(".ant-pagination-item", "2").click(); + cy.contains(".ProposalCard", "Fake Proposal #12 CORE_DEV WIP"); - // browse to create via header link - cy.get('.Header a[href="/create"]').click(); - cy.title().should("include", "Grant.io - Create a Proposal"); + cy.contains("a", "Requests").click(); - // browse to explore via header link - cy.get('.Header a[href="/proposals"]').click(); - cy.title().should("include", "Grant.io - Browse proposals"); + cy.contains("a", "Start a Proposal").click(); + cy.title().should("include", "ZF Grants - Sign in"); + cy.contains("Authorization required"); - // browse footer links - cy.get('.Footer a[href="/about"]').click(); - cy.title().should("include", "Grant.io - About"); - cy.get('.Footer a[href="/contact"]').click(); - cy.title().should("include", "Grant.io - Contact"); - cy.get('.Footer a[href="/tos"]').click(); - cy.title().should("include", "Grant.io - Terms of Service"); - cy.get('.Footer a[href="/privacy"]').click(); - cy.title().should("include", "Grant.io - Privacy Policy"); + // cy.contains("a", "About").click(); // external site + cy.contains("a", "Contact").click({ force: true }); + cy.contains("h1", "Contact"); + cy.contains("a", "Terms of").click({ force: true }); + cy.contains("h1", "Terms"); + cy.contains("a", "Privacy").click({ force: true }); + cy.contains("h1", "Privacy"); + cy.contains("a", "Code of").click({ force: true }); + cy.contains("h1", "Community"); + + cy.contains("a", "Sign in").click(); + cy.contains("a", "Recover your").click(); + cy.contains("h1", "Account Recovery"); + cy.contains("a", "Sign in").click(); + cy.contains("a", "Create a").click(); }); }); diff --git a/e2e/cypress/integration/create.cancel.spec.ts b/e2e/cypress/integration/create.cancel.spec.ts deleted file mode 100644 index b1c682af..00000000 --- a/e2e/cypress/integration/create.cancel.spec.ts +++ /dev/null @@ -1,47 +0,0 @@ -/// -import { - loadWeb3, - increaseTime, - syncTimeWithEvm, - randomString -} from "../helpers"; -import { createDemoProposal, authenticateUser } from "../parts"; - -describe("proposal", () => { - const id = randomString(); - const title = `[${id}] e2e create cancel`; - const amount = "1"; - - afterEach(function() { - if (this.currentTest.state === "failed") { - (Cypress as any).runner.stop(); - } - }); - - it("authenticates and creates if necessary", () => { - authenticateUser(cy, 0); - }); - - it("creates demo proposal", () => { - createDemoProposal(cy, title, amount); - }); - - it("cancels the proposal", () => { - cy.contains(".Proposal-top-main-menu > .ant-btn", "Actions").click(); - cy.contains(".ant-dropdown-menu-item", "Cancel proposal").click(); - cy.contains(".ant-modal-footer > div button", "Confirm").click(); - cy.contains("body", "Proposal didn’t get funded", { timeout: 20000 }); - cy.get(".ant-modal-wrap").should("not.be.visible"); - cy.contains(".Proposal-top-main-menu > .ant-btn", "Actions").click(); - cy.contains(".ant-dropdown-menu-item", "Cancel proposal").should( - "have.attr", - "aria-disabled", - "true" - ); - }); - - it("should appear unfunded to outsiders (account 9)", () => { - cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(9) })); - cy.contains("body", "Proposal didn’t get funded", { timeout: 20000 }); - }); -}); diff --git a/e2e/cypress/integration/create.flow.spec.ts b/e2e/cypress/integration/create.flow.spec.ts deleted file mode 100644 index bb8f493a..00000000 --- a/e2e/cypress/integration/create.flow.spec.ts +++ /dev/null @@ -1,173 +0,0 @@ -/// -import { loadWeb3, randomString, randomHex, syncTimeWithEvm } from "../helpers"; -import { authenticateUser } from "../parts"; - -describe("create.flow", () => { - const time = new Date().toLocaleString(); - const id = randomString(); - const randomEthHex = randomHex(32); - const nextYear = new Date().getUTCFullYear() + 1; - const proposal = { - title: `[${id}] e2e create flow`, - brief: "e2e brief", - category: "Community", // .anticon-team - targetAmount: 5, - body: `#### e2e Proposal ${id} {enter} **created** ${time} `, - team: [ - { - name: "Alisha Endtoend", - title: "QA Robot0", - ethAddress: `0x0000${randomEthHex}0000`, - emailAddress: `qa.alisha.${id}@grant.io` - }, - { - name: "Billy Endtoend", - title: "QA Robot1", - ethAddress: `0x1111${randomEthHex}1111`, - emailAddress: `qa.billy.${id}@grant.io` - } - ], - milestones: [ - { - title: `e2e Milestone ${id} 0`, - body: `e2e Milestone ${id} {enter} body 0`, - date: { - y: nextYear, - m: "Jan", - expect: "January " + nextYear - } - }, - { - title: `e2e Milestone ${id} 1`, - body: `e2e Milestone ${id} {enter} body 1`, - date: { - y: nextYear, - m: "Feb", - expect: "February " + nextYear - } - } - ] - }; - - afterEach(function() { - if (this.currentTest.state === "failed") { - (Cypress as any).runner.stop(); - } - }); - - context("create flow wizard", () => { - it("authenticates and creates if necessary", () => { - authenticateUser(cy, 0); - }); - - it("create flow step 1", () => { - cy.get('[href="/create"]').click(); - syncTimeWithEvm(cy); - cy.get('.CreateFlow input[name="title"]').type(proposal.title); - cy.get('.CreateFlow textarea[name="brief"]').type(proposal.brief); - cy.contains("Select a category").click(); - cy.get(".ant-select-dropdown li .anticon-team").click(); - cy.get('.CreateFlow input[name="amountToRaise"]').type( - "" + proposal.targetAmount - ); - cy.wait(1000); - cy.contains(".CreateFlow-footer-button", "Continue").click(); - }); - - it("create flow step 2", () => { - cy.get("button.TeamForm-add").click(); - cy.get('.TeamMember-info input[name="name"]').type(proposal.team[1].name); - cy.get('.TeamMember-info input[name="title"]').type( - proposal.team[1].title - ); - cy.get('.TeamMember-info input[name="ethAddress"]').type( - proposal.team[1].ethAddress - ); - cy.get('.TeamMember-info input[name="emailAddress"]').type( - proposal.team[1].emailAddress - ); - cy.get("button") - .contains("Save changes") - .click({ force: true }); - cy.wait(1000); - cy.contains(".CreateFlow-footer-button", "Continue").click(); - }); - - it("create flow step 3", () => { - cy.get(".DraftEditor-editorContainer > div").type(proposal.body); - cy.get(".mde-tabs > :nth-child(2)").click(); - cy.wait(1000); - cy.contains(".CreateFlow-footer-button", "Continue").click(); - }); - - it("create flow step 4", () => { - cy.get('input[name="title"]').type(proposal.milestones[0].title); - cy.get('textarea[name="body"]').type(proposal.milestones[0].body); - cy.get('input[placeholder="Expected completion date"]').click(); - cy.get(".ant-calendar-month-panel-next-year-btn").click(); - cy.get(".ant-calendar-month-panel-month") - .contains(proposal.milestones[0].date.m) - .click(); - cy.get(".ant-calendar-picker-input").should( - "have.value", - proposal.milestones[0].date.expect - ); - cy.get("button") - .contains("Add another milestone") - .click({ force: true }); - cy.get('input[name="title"]') - .eq(1) - .type(proposal.milestones[1].title); - cy.get('textarea[name="body"]') - .eq(1) - .type(proposal.milestones[1].body); - cy.get('input[placeholder="Expected completion date"]') - .eq(1) - .click(); - cy.get(".ant-calendar-month-panel-next-year-btn").click(); - cy.get(".ant-calendar-month-panel-month") - .contains(proposal.milestones[1].date.m) - .click(); - cy.get(".ant-calendar-picker-input") - .eq(1) - .should("have.value", proposal.milestones[1].date.expect); - cy.wait(1000); - cy.contains(".CreateFlow-footer-button", "Continue").click(); - }); - - it("create flow step 5", () => { - cy.window() - .then(w => (w as any).web3.eth.getAccounts()) - .then(accts => { - cy.get('input[name="payOutAddress"]').type(accts[0]); - cy.get("button") - .contains("Add another trustee") - .click({ force: true }); - cy.get( - 'input[placeholder="0x8B0B72F8bDE212991135668922fD5acE557DE6aB"]' - ) - .eq(1) - .type(accts[1]); - cy.get('input[name="deadline"][value="2592000"]').click({ - force: true - }); - cy.get('input[name="milestoneDeadline"][value="259200"]').click({ - force: true - }); - }); - cy.wait(1000); - cy.contains(".CreateFlow-footer-button", "Continue").click(); - }); - - it("publishes the proposal", () => { - cy.get("button") - .contains("Publish") - .click(); - cy.get(".CreateFinal-loader-text").contains("Deploying contract..."); - cy.get(".CreateFinal-message-text a", { timeout: 20000 }) - .contains("Click here") - .click(); - cy.get(".Proposal-top-main-title").contains(proposal.title); - }); - }); -}); diff --git a/e2e/cypress/integration/create.fund.cancel.spec.ts b/e2e/cypress/integration/create.fund.cancel.spec.ts deleted file mode 100644 index 85250d42..00000000 --- a/e2e/cypress/integration/create.fund.cancel.spec.ts +++ /dev/null @@ -1,61 +0,0 @@ -/// -import { - loadWeb3, - increaseTime, - syncTimeWithEvm, - randomString -} from "../helpers"; -import { createDemoProposal, authenticateUser } from "../parts"; - -describe("create.fund.cancel", () => { - const id = randomString(); - const title = `[${id}] e2e create fund cancel`; - const amount = "1"; - - afterEach(function() { - if (this.currentTest.state === "failed") { - (Cypress as any).runner.stop(); - } - }); - - it("authenticates and creates if necessary", () => { - authenticateUser(cy, 0); - }); - - it("create demo proposal", () => { - createDemoProposal(cy, title, amount); - }); - - it("funds the proposal with account 5", () => { - cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(5) })); - cy.get(".ant-input", { timeout: 20000 }).type(amount); - cy.get(".ant-form > .ant-btn").click(); - cy.get(".ProposalCampaignBlock-fundingOver", { timeout: 20000 }).contains( - "Proposal has been funded" - ); - }); - - it("cancels the proposal (refund contributors)", () => { - cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(0) })); - cy.contains(".Proposal-top-main-menu > .ant-btn", "Actions").click(); - cy.contains(".ant-dropdown-menu-item", "Refund contributors").click(); - cy.contains(".ant-modal-footer > div button", "Confirm").click(); - cy.get(".ant-modal-wrap", { timeout: 20000 }).should("not.be.visible"); - cy.contains(".Proposal-top-main-menu > .ant-btn", "Actions").click(); - cy.contains(".ant-dropdown-menu-item", "Refund contributors").should( - "have.attr", - "aria-disabled", - "true" - ); - }); - - it("refunds the contributor (account 5)", () => { - cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(5) })); - cy.contains(".ant-tabs-nav > :nth-child(1) > :nth-child(4)", "Refund", { - timeout: 20000 - }).click(); - // force disables cypress' auto scrolling which messes up UI in this case - cy.contains(".ant-btn", "Get your refund").click({ force: true }); - cy.contains("body", "Your refund has been processed", { timeout: 20000 }); - }); -}); diff --git a/e2e/cypress/integration/create.fund.complete.minority-no-votes.spec.ts b/e2e/cypress/integration/create.fund.complete.minority-no-votes.spec.ts deleted file mode 100644 index 9fa5580a..00000000 --- a/e2e/cypress/integration/create.fund.complete.minority-no-votes.spec.ts +++ /dev/null @@ -1,126 +0,0 @@ -/// -import { - loadWeb3, - randomString, - syncTimeWithEvm, - increaseTime -} from "../helpers"; -import { createDemoProposal, fundProposal, authenticateUser } from "../parts"; - -describe("create.fund.complete.minority-no-votes", () => { - const id = randomString(); - const title = `[${id}] e2e minority no-votes complete`; - const amount = "1"; - - afterEach(function() { - if (this.currentTest.state === "failed") { - //(Cypress as any).runner.stop(); - this.skip(); - } - }); - - it("authenticates and creates if necessary", () => { - authenticateUser(cy, 0); - }); - - it("creates demo proposal", () => { - createDemoProposal(cy, title, amount); - }); - - it("funds the proposal from accounts 5, 6 & 7", () => { - fundProposal(cy, 5, 0.1); - fundProposal(cy, 6, 0.2); - fundProposal(cy, 7, 0.7); - cy.get(".ProposalCampaignBlock-fundingOver", { timeout: 20000 }).contains( - "Proposal has been funded" - ); - }); - - it("receives initial payout", () => { - // MILESTONE 1 - syncTimeWithEvm(cy); - cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(0) })); - cy.get(".MilestoneAction-top > div > .ant-btn", { timeout: 20000 }).click(); - cy.contains( - ".MilestoneAction-top > div > .ant-btn", - "Receive initial payout", - { timeout: 20000 } - ).click(); - }); - - it("requests milestone 2 payout", () => { - // MILESTONE 2 - cy.contains( - ".MilestoneAction-top > div > .ant-btn", - "Request milestone payout", - { timeout: 20000 } - ).click(); - cy.contains(".MilestoneAction-progress-text", "voted against payout", { - timeout: 20000 - }); - }); - - it("minority funder (acct 5) votes no", () => { - // VOTE NO - syncTimeWithEvm(cy); - cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(5) })); - cy.contains(".ant-btn", "Vote against payout", { timeout: 20000 }) - .click() - .should("have.class", "ant-btn-loading"); - cy.contains(".ant-btn", "Revert vote against payout", { timeout: 20000 }); - }); - - it("expires milestone 2 voting period & receives payout", () => { - // EXPIRE - increaseTime(cy, 70000); - // RECEIVE PAYOUT - cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(0) })); - cy.contains( - ".MilestoneAction-top > div > .ant-btn", - "Receive milestone payout", - { timeout: 20000 } - ).click(); - }); - - it("requests milestone 3 payout", () => { - // MILESTONE 3 - cy.contains( - ".MilestoneAction-top > div > .ant-btn", - "Request milestone payout", - { timeout: 20000 } - ).click(); - cy.contains(".MilestoneAction-progress-text", "voted against payout", { - timeout: 20000 - }); - }); - - it("minority funder (acct 5) votes no", () => { - // VOTE NO - syncTimeWithEvm(cy); - cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(5) })); - cy.contains(".ant-btn", "Vote against payout", { timeout: 20000 }) - .click() - .should("have.class", "ant-btn-loading"); - cy.contains(".ant-btn", "Revert vote against payout", { timeout: 20000 }); - }); - - it("expires milestone 3 voting period & receives payout", () => { - // EXPIRE - increaseTime(cy, 70000); - // RECEIVE PAYOUT - cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(0) })); - cy.contains( - ".MilestoneAction-top > div > .ant-btn", - "Receive milestone payout", - { timeout: 20000 } - ).click(); - }); - - it("should not have receive button", () => { - cy.contains( - ".MilestoneAction-top > div > .ant-btn", - "Receive milestone payout", - { timeout: 20000 } - ).should("not.exist"); - }); -}); diff --git a/e2e/cypress/integration/create.fund.complete.spec.ts b/e2e/cypress/integration/create.fund.complete.spec.ts deleted file mode 100644 index ae819fe0..00000000 --- a/e2e/cypress/integration/create.fund.complete.spec.ts +++ /dev/null @@ -1,103 +0,0 @@ -/// -import { - loadWeb3, - increaseTime, - syncTimeWithEvm, - randomString -} from "../helpers"; -import { createDemoProposal, authenticateUser } from "../parts"; - -describe("create.fund.complete", () => { - const id = randomString(); - const title = `[${id}] e2e create fund complete`; - const amount = "1"; - - afterEach(function() { - if (this.currentTest.state === "failed") { - //(Cypress as any).runner.stop(); - this.skip(); - } - }); - - it("authenticates and creates if necessary", () => { - authenticateUser(cy, 0); - }); - - it("create demo proposal", () => { - createDemoProposal(cy, title, amount); - }); - - it("funds the proposal with account 5", () => { - cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(5) })); - cy.get(".ant-input", { timeout: 20000 }).type(amount); - cy.get(".ant-form > .ant-btn").click(); - cy.get(".ProposalCampaignBlock-fundingOver", { timeout: 20000 }).contains( - "Proposal has been funded" - ); - }); - - it("receives initial payout", () => { - // MILESTONE 1 - cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(0) })); - cy.contains( - ".MilestoneAction-top > div > .ant-btn", - "Request initial payout", - { timeout: 20000 } - ) - .as("RequestPayout") - .click(); - cy.contains( - ".MilestoneAction-top > div > .ant-btn", - "Receive initial payout", - { timeout: 20000 } - ).click(); - }); - - it("requests and receives milestone 2 payout", () => { - // MILESTONE 2 - cy.contains( - ".MilestoneAction-top > div > .ant-btn", - "Request milestone payout", - { timeout: 20000 } - ).click(); - cy.contains(".MilestoneAction-progress-text", "voted against payout", { - timeout: 20000 - }); - // EXPIRE - increaseTime(cy, 70000); - cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(0) })); - cy.contains( - ".MilestoneAction-top > div > .ant-btn", - "Receive milestone payout", - { timeout: 20000 } - ).click(); - }); - - it("requests and receives milestone 3 payout", () => { - // MILESTONE 3 - cy.contains( - ".MilestoneAction-top > div > .ant-btn", - "Request milestone payout", - { timeout: 20000 } - ).click(); - cy.contains(".MilestoneAction-progress-text", "voted against payout", { - timeout: 20000 - }); - // EXPIRE - increaseTime(cy, 70000); - cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(0) })); - cy.contains( - ".MilestoneAction-top > div > .ant-btn", - "Receive milestone payout", - { timeout: 20000 } - ).click(); - }); - - it("should not have receive button", () => { - cy.contains( - ".MilestoneAction-top > div > .ant-btn", - "Receive milestone payout", - { timeout: 20000 } - ).should("not.exist"); - }); -}); diff --git a/e2e/cypress/integration/create.fund.ms2.majority-no-vote.spec.ts b/e2e/cypress/integration/create.fund.ms2.majority-no-vote.spec.ts deleted file mode 100644 index 41b40f77..00000000 --- a/e2e/cypress/integration/create.fund.ms2.majority-no-vote.spec.ts +++ /dev/null @@ -1,60 +0,0 @@ -/// -import { loadWeb3, randomString, syncTimeWithEvm } from "../helpers"; -import { createDemoProposal, fundProposal, authenticateUser } from "../parts"; - -describe("create.fund.ms2.majority-no-vote", () => { - const id = randomString(); - const title = `[${id}] e2e ms2 majority no-vote`; - const amount = "1"; - - it("authenticates and creates if necessary", () => { - authenticateUser(cy, 0); - }); - - it("creates demo proposal", () => { - createDemoProposal(cy, title, amount); - }); - - it("fund the proposal with 5th account", () => { - fundProposal(cy, 5, 1); - cy.get(".ProposalCampaignBlock-fundingOver", { timeout: 20000 }).contains( - "Proposal has been funded" - ); - }); - - it("receives initial payout for milestone 1", () => { - // MILESTONE 1 - syncTimeWithEvm(cy); - cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(0) })); - cy.get(".MilestoneAction-top > div > .ant-btn", { timeout: 20000 }).click(); - cy.contains( - ".MilestoneAction-top > div > .ant-btn", - "Receive initial payout", - { timeout: 20000 } - ).click(); - cy.contains( - ".MilestoneAction-top > div > .ant-btn", - "Request milestone payout", - { timeout: 20000 } - ); - }); - - it("requests milestone 2 payout", () => { - // MILESTONE 2 - cy.contains( - ".MilestoneAction-top > div > .ant-btn", - "Request milestone payout", - { timeout: 20000 } - ).click(); - cy.contains(".MilestoneAction-progress-text", "voted against payout", { - timeout: 20000 - }); - }); - - it("vote against milestone 2 payout as account 5", () => { - syncTimeWithEvm(cy); - cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(5) })); - cy.contains(".ant-btn", "Vote against payout", { timeout: 20000 }).click(); - cy.contains(".ant-btn", "Revert vote against payout", { timeout: 20000 }); - }); -}); diff --git a/e2e/cypress/integration/create.fund.ms2.no-vote.re-vote.spec.ts b/e2e/cypress/integration/create.fund.ms2.no-vote.re-vote.spec.ts deleted file mode 100644 index eb1f7a65..00000000 --- a/e2e/cypress/integration/create.fund.ms2.no-vote.re-vote.spec.ts +++ /dev/null @@ -1,86 +0,0 @@ -/// -import { - loadWeb3, - randomString, - syncTimeWithEvm, - increaseTime -} from "../helpers"; -import { createDemoProposal, fundProposal, authenticateUser } from "../parts"; - -describe("create.fund.ms2.no-vote.re-vote", () => { - const id = randomString(); - const title = `[${id}] e2e ms2 no-vote expire re-vote`; - const amount = "1"; - - afterEach(function() { - if (this.currentTest.state === "failed") { - //(Cypress as any).runner.stop(); - this.skip(); - } - }); - - it("authenticates and creates if necessary", () => { - authenticateUser(cy, 0); - }); - - it("creates demo proposal", () => { - createDemoProposal(cy, title, amount); - }); - - it("fund the proposal with 5th account", () => { - fundProposal(cy, 5, 1); - cy.get(".ProposalCampaignBlock-fundingOver", { timeout: 20000 }).contains( - "Proposal has been funded" - ); - }); - - it("receives initial payout for milestone 1", () => { - // MILESTONE 1 - syncTimeWithEvm(cy); - cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(0) })); - cy.get(".MilestoneAction-top > div > .ant-btn", { timeout: 20000 }).click(); - cy.contains( - ".MilestoneAction-top > div > .ant-btn", - "Receive initial payout", - { timeout: 20000 } - ).click(); - }); - - it("request milestone 2 payout", () => { - // MILESTONE 2 - cy.contains( - ".MilestoneAction-top > div > .ant-btn", - "Request milestone payout", - { timeout: 20000 } - ).click(); - cy.contains(".MilestoneAction-progress-text", "voted against payout", { - timeout: 20000 - }); - }); - - it("vote against milestone 2 payout as 5th account", () => { - // reload page with 5th account - syncTimeWithEvm(cy); - cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(5) })); - cy.contains(".ant-btn", "Vote against payout", { timeout: 20000 }) - .click() - .should("have.class", "ant-btn-loading"); - cy.contains(".ant-btn", "Revert vote against payout", { timeout: 20000 }); - }); - - it("milestone 2 vote expires and payout is requested again", () => { - // EXPIRE - increaseTime(cy, 70000); - cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(0) })); - cy.contains("Payout was voted against"); - // RE-REQUEST PAYOUT - cy.contains( - ".MilestoneAction-top > div > .ant-btn", - "Request milestone payout", - { timeout: 20000 } - ).click(); - cy.contains(".MilestoneAction-progress-text", "voted against payout", { - timeout: 20000 - }); - }); -}); diff --git a/e2e/cypress/integration/create.fund.ms2.refund-after-payout.spec.ts b/e2e/cypress/integration/create.fund.ms2.refund-after-payout.spec.ts deleted file mode 100644 index f52f2fb0..00000000 --- a/e2e/cypress/integration/create.fund.ms2.refund-after-payout.spec.ts +++ /dev/null @@ -1,69 +0,0 @@ -/// -import { - loadWeb3, - randomString, - syncTimeWithEvm, - increaseTime -} from "../helpers"; -import { createDemoProposal, fundProposal, authenticateUser } from "../parts"; - -describe("create.fund.ms2.refund-after-payout", () => { - const id = randomString(); - const title = `[${id}] e2e ms2 refund after payout`; - const amount = "1"; - - afterEach(function() { - if (this.currentTest.state === "failed") { - //(Cypress as any).runner.stop(); - this.skip(); - } - }); - - it("authenticates and creates if necessary", () => { - authenticateUser(cy, 0); - }); - - it("creates demo proposal", () => { - createDemoProposal(cy, title, amount); - }); - - it("fund the proposal with account 5", () => { - fundProposal(cy, 5, 1); - cy.get(".ProposalCampaignBlock-fundingOver", { timeout: 20000 }).contains( - "Proposal has been funded" - ); - }); - - it("receives initial payout for milestone 1", () => { - // MILESTONE 1 - syncTimeWithEvm(cy); - cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(0) })); - cy.get(".MilestoneAction-top > div > .ant-btn", { timeout: 20000 }).click(); - cy.contains( - ".MilestoneAction-top > div > .ant-btn", - "Receive initial payout", - { timeout: 20000 } - ).click(); - cy.contains( - ".MilestoneAction-top > div > .ant-btn", - "Request milestone payout", - { timeout: 20000 } - ); - }); - - it("majority refund vote and get refund (account 5)", () => { - // REFUND - cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(5) })); - cy.contains(".ant-tabs-nav > :nth-child(1) > :nth-child(4)", "Refund", { - timeout: 20000 - }).click(); - // INCREASE TIME - increaseTime(cy, 70000); - // force disables cypress' auto scrolling which messes up UI in this case - cy.contains(".ant-btn", "Vote for refund").click({ force: true }); - cy.contains(".ant-btn", "Get your refund", { timeout: 20000 }).click({ - force: true - }); - cy.contains("body", "Your refund has been processed", { timeout: 20000 }); - }); -}); diff --git a/e2e/cypress/integration/create.fund.ms2.revert-no-vote.spec.ts b/e2e/cypress/integration/create.fund.ms2.revert-no-vote.spec.ts deleted file mode 100644 index ca7ddf7a..00000000 --- a/e2e/cypress/integration/create.fund.ms2.revert-no-vote.spec.ts +++ /dev/null @@ -1,93 +0,0 @@ -/// -import { - loadWeb3, - randomString, - syncTimeWithEvm, - increaseTime -} from "../helpers"; -import { createDemoProposal, fundProposal, authenticateUser } from "../parts"; - -describe("create.fund.ms2.revert-no-vote", () => { - const id = randomString(); - const title = `[${id}] e2e ms2 revert no-vote`; - const amount = "1"; - - afterEach(function() { - if (this.currentTest.state === "failed") { - //(Cypress as any).runner.stop(); - this.skip(); - } - }); - - it("authenticates and creates if necessary", () => { - authenticateUser(cy, 0); - }); - - it("creates demo proposal", () => { - createDemoProposal(cy, title, amount); - }); - - it("funds the proposal with 5th account", () => { - fundProposal(cy, 5, 1); - cy.get(".ProposalCampaignBlock-fundingOver", { timeout: 20000 }).contains( - "Proposal has been funded" - ); - }); - - it("receives initial payout for milestone 1", () => { - // MILESTONE 1 - syncTimeWithEvm(cy); - cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(0) })); - cy.get(".MilestoneAction-top > div > .ant-btn", { timeout: 20000 }).click(); - cy.contains( - ".MilestoneAction-top > div > .ant-btn", - "Receive initial payout", - { timeout: 20000 } - ).click(); - }); - - it("requests milestone 2 payout", () => { - // MILESTONE 2 - cy.contains( - ".MilestoneAction-top > div > .ant-btn", - "Request milestone payout", - { timeout: 20000 } - ).click(); - cy.contains(".MilestoneAction-progress-text", "voted against payout", { - timeout: 20000 - }); - }); - - it("votes against milestone 2 payout as account 5 and then reverts the vote", () => { - // NO VOTE... REVERT - syncTimeWithEvm(cy); - cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(5) })); - cy.contains(".ant-btn", "Vote against payout", { timeout: 20000 }) - .click() - .should("have.class", "ant-btn-loading"); - cy.contains(".ant-btn", "Revert vote against payout", { timeout: 20000 }) - .click() - .should("have.class", "ant-btn-loading"); - cy.contains(".ant-btn", "Vote against payout", { timeout: 20000 }); - }); - - it("milestone 2 vote expires and payout is received", () => { - // EXPIRE - increaseTime(cy, 70000); - // PAYOUT - cy.url().then(url => cy.visit(url, { onBeforeLoad: loadWeb3(0) })); - cy.contains( - ".MilestoneAction-top > div > .ant-btn", - "Receive milestone payout" - ).click(); - }); - - it("milestone 3 becomes active", () => { - // MILESTONE 3 - cy.contains( - ".MilestoneAction-top > div > .ant-btn", - "Request milestone payout", - { timeout: 20000 } - ); - }); -}); diff --git a/e2e/cypress/integration/proposal.fund.spec.ts b/e2e/cypress/integration/proposal.fund.spec.ts new file mode 100644 index 00000000..edcb8b85 --- /dev/null +++ b/e2e/cypress/integration/proposal.fund.spec.ts @@ -0,0 +1,77 @@ +/// + +describe("proposal.fund", () => { + let stubs: any; + before(() => { + cy.request("http://localhost:5000/api/v1/e2e/setup").then( + r => (stubs = r.body) + ); + }); + + it("should be able to make anonymous contributions", () => { + cy.visit("/proposals"); + cy.title().should("include", "ZF Grants - Browse proposals"); + cy.get("html").then(el => (el[0].style.scrollBehavior = "auto")); + + cy.contains(".ant-radio-wrapper", "Funding req").click(); + cy.contains( + ".ProposalCard", + "Fake Proposal #11 COMMUNITY FUNDING_REQUIRED" + ).click(); + + cy.get("input[name='amountToRaise']").type("100"); + cy.contains("Cannot exceed maximum"); + cy.get("input[name='amountToRaise']") + .clear() + .type("23.456"); + cy.contains("button", "Fund this").click(); + cy.contains("anonymous"); + cy.contains("button", "I accept").click(); + cy.contains("button", "I’ve sent it").click(); + cy.contains("button", "Done").click(); + }); + + it("should be able to make contributions", () => { + cy.visit("/auth/sign-in"); + cy.get("html").then(el => (el[0].style.scrollBehavior = "auto")); + + // login + cy.get("input[placeholder='email']") + .clear() + .type(stubs.defaultUser.emailAddress); + cy.get("input[placeholder='password']") + .clear() + .type(stubs.defaultUser.password); + cy.contains("button", "Sign in").click(); + + cy.contains("default Endtoenderson"); + + cy.contains("a", "Proposals").click(); + cy.title().should("include", "ZF Grants - Browse proposals"); + cy.contains(".ant-radio-wrapper", "Funding req").click(); + cy.contains( + ".ProposalCard", + "Fake Proposal #11 COMMUNITY FUNDING_REQUIRED" + ).click(); + + cy.get("input[name='amountToRaise']").type("23.456"); + cy.contains("button", "Fund this").click(); + cy.contains("Make your contribution"); + cy.contains("button", "I’ve sent it").click(); + cy.contains("a", "funded tab").click(); + cy.contains(".ant-tabs-tab-active", "Funded"); + cy.contains( + ".ProfileContribution", + "Fake Proposal #11 COMMUNITY FUNDING_REQUIRED" + ); + cy.contains(".ProfileContribution", "23.456"); + + cy.request("http://localhost:5000/api/v1/e2e/contribution/confirm"); + cy.contains( + ".ProfileContribution a", + "Fake Proposal #11 COMMUNITY FUNDING_REQUIRED" + ).click(); + cy.contains("Proposal has been funded"); + cy.contains("a", "default Endtoenderson").click(); + }); +}); diff --git a/e2e/cypress/integration/sandbox.ts b/e2e/cypress/integration/sandbox.ts deleted file mode 100644 index 48267885..00000000 --- a/e2e/cypress/integration/sandbox.ts +++ /dev/null @@ -1,15 +0,0 @@ -/// -import { - loadWeb3, - increaseTime, - randomString, - syncTimeWithEvm -} from "../helpers"; - -// describe("sandbox", () => { -// it("how to increase time", () => { -// cy.visit("http://localhost:3000", { onBeforeLoad: loadWeb3(0) }); -// // increase time on browser and ganache -// increaseTime(cy, 60000); -// }); -// }); diff --git a/e2e/cypress/support/index.js b/e2e/cypress/support/index.js index d68db96d..48f0d7f4 100644 --- a/e2e/cypress/support/index.js +++ b/e2e/cypress/support/index.js @@ -14,7 +14,12 @@ // *********************************************************** // Import commands.js using ES2015 syntax: -import './commands' +import "./commands"; // Alternatively you can use CommonJS syntax: // require('./commands') + +Cypress.on("uncaught:exception", (err, runnable) => { + // do not stop for uncaught exceptions (wave.js) + return false; +}); diff --git a/e2e/package.json b/e2e/package.json index dd36cf1e..00ce5f5b 100644 --- a/e2e/package.json +++ b/e2e/package.json @@ -8,13 +8,10 @@ "cypress:run": "cypress run" }, "dependencies": { - "@cypress/webpack-preprocessor": "^2.0.1", - "@types/web3": "^1.0.3", - "cypress": "^3.1.0", - "eth-sig-util": "^2.1.0", + "@cypress/webpack-preprocessor": "^4.0.3", + "cypress": "^3.1.5", "ts-loader": "^5.0.0", "typescript": "^3.0.3", - "web3": "^1.0.0-beta.36", "webpack": "^4.17.2" } } diff --git a/e2e/yarn.lock b/e2e/yarn.lock index 5ee8e43b..a5831ac3 100644 --- a/e2e/yarn.lock +++ b/e2e/yarn.lock @@ -2,31 +2,623 @@ # yarn lockfile v1 +"@babel/code-frame@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.0.0.tgz#06e2ab19bdb535385559aabb5ba59729482800f8" + integrity sha512-OfC2uemaknXr87bdLUkWog7nYuliM9Ij5HUcajsVcMCpQrcLmtxRbVFTIqmcSkSeYRBFBRxs2FiUqFJDLdiebA== + dependencies: + "@babel/highlight" "^7.0.0" + +"@babel/core@^7.0.1": + version "7.3.4" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.3.4.tgz#921a5a13746c21e32445bf0798680e9d11a6530b" + integrity sha512-jRsuseXBo9pN197KnDwhhaaBzyZr2oIcLHHTt2oDdQrej5Qp57dCCJafWx5ivU8/alEYDpssYqv1MUqcxwQlrA== + dependencies: + "@babel/code-frame" "^7.0.0" + "@babel/generator" "^7.3.4" + "@babel/helpers" "^7.2.0" + "@babel/parser" "^7.3.4" + "@babel/template" "^7.2.2" + "@babel/traverse" "^7.3.4" + "@babel/types" "^7.3.4" + convert-source-map "^1.1.0" + debug "^4.1.0" + json5 "^2.1.0" + lodash "^4.17.11" + resolve "^1.3.2" + semver "^5.4.1" + source-map "^0.5.0" + +"@babel/generator@^7.3.4": + version "7.3.4" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.3.4.tgz#9aa48c1989257877a9d971296e5b73bfe72e446e" + integrity sha512-8EXhHRFqlVVWXPezBW5keTiQi/rJMQTg/Y9uVCEZ0CAF3PKtCCaVRnp64Ii1ujhkoDhhF1fVsImoN4yJ2uz4Wg== + dependencies: + "@babel/types" "^7.3.4" + jsesc "^2.5.1" + lodash "^4.17.11" + source-map "^0.5.0" + trim-right "^1.0.1" + +"@babel/helper-annotate-as-pure@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.0.0.tgz#323d39dd0b50e10c7c06ca7d7638e6864d8c5c32" + integrity sha512-3UYcJUj9kvSLbLbUIfQTqzcy5VX7GRZ/CCDrnOaZorFFM01aXp1+GJwuFGV4NDDoAS+mOUyHcO6UD/RfqOks3Q== + dependencies: + "@babel/types" "^7.0.0" + +"@babel/helper-builder-binary-assignment-operator-visitor@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.1.0.tgz#6b69628dfe4087798e0c4ed98e3d4a6b2fbd2f5f" + integrity sha512-qNSR4jrmJ8M1VMM9tibvyRAHXQs2PmaksQF7c1CGJNipfe3D8p+wgNwgso/P2A2r2mdgBWAXljNWR0QRZAMW8w== + dependencies: + "@babel/helper-explode-assignable-expression" "^7.1.0" + "@babel/types" "^7.0.0" + +"@babel/helper-call-delegate@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/helper-call-delegate/-/helper-call-delegate-7.1.0.tgz#6a957f105f37755e8645343d3038a22e1449cc4a" + integrity sha512-YEtYZrw3GUK6emQHKthltKNZwszBcHK58Ygcis+gVUrF4/FmTVr5CCqQNSfmvg2y+YDEANyYoaLz/SHsnusCwQ== + dependencies: + "@babel/helper-hoist-variables" "^7.0.0" + "@babel/traverse" "^7.1.0" + "@babel/types" "^7.0.0" + +"@babel/helper-define-map@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/helper-define-map/-/helper-define-map-7.1.0.tgz#3b74caec329b3c80c116290887c0dd9ae468c20c" + integrity sha512-yPPcW8dc3gZLN+U1mhYV91QU3n5uTbx7DUdf8NnPbjS0RMwBuHi9Xt2MUgppmNz7CJxTBWsGczTiEp1CSOTPRg== + dependencies: + "@babel/helper-function-name" "^7.1.0" + "@babel/types" "^7.0.0" + lodash "^4.17.10" + +"@babel/helper-explode-assignable-expression@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.1.0.tgz#537fa13f6f1674df745b0c00ec8fe4e99681c8f6" + integrity sha512-NRQpfHrJ1msCHtKjbzs9YcMmJZOg6mQMmGRB+hbamEdG5PNpaSm95275VD92DvJKuyl0s2sFiDmMZ+EnnvufqA== + dependencies: + "@babel/traverse" "^7.1.0" + "@babel/types" "^7.0.0" + +"@babel/helper-function-name@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.1.0.tgz#a0ceb01685f73355d4360c1247f582bfafc8ff53" + integrity sha512-A95XEoCpb3TO+KZzJ4S/5uW5fNe26DjBGqf1o9ucyLyCmi1dXq/B3c8iaWTfBk3VvetUxl16e8tIrd5teOCfGw== + dependencies: + "@babel/helper-get-function-arity" "^7.0.0" + "@babel/template" "^7.1.0" + "@babel/types" "^7.0.0" + +"@babel/helper-get-function-arity@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/helper-get-function-arity/-/helper-get-function-arity-7.0.0.tgz#83572d4320e2a4657263734113c42868b64e49c3" + integrity sha512-r2DbJeg4svYvt3HOS74U4eWKsUAMRH01Z1ds1zx8KNTPtpTL5JAsdFv8BNyOpVqdFhHkkRDIg5B4AsxmkjAlmQ== + dependencies: + "@babel/types" "^7.0.0" + +"@babel/helper-hoist-variables@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.0.0.tgz#46adc4c5e758645ae7a45deb92bab0918c23bb88" + integrity sha512-Ggv5sldXUeSKsuzLkddtyhyHe2YantsxWKNi7A+7LeD12ExRDWTRk29JCXpaHPAbMaIPZSil7n+lq78WY2VY7w== + dependencies: + "@babel/types" "^7.0.0" + +"@babel/helper-member-expression-to-functions@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.0.0.tgz#8cd14b0a0df7ff00f009e7d7a436945f47c7a16f" + integrity sha512-avo+lm/QmZlv27Zsi0xEor2fKcqWG56D5ae9dzklpIaY7cQMK5N8VSpaNVPPagiqmy7LrEjK1IWdGMOqPu5csg== + dependencies: + "@babel/types" "^7.0.0" + +"@babel/helper-module-imports@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.0.0.tgz#96081b7111e486da4d2cd971ad1a4fe216cc2e3d" + integrity sha512-aP/hlLq01DWNEiDg4Jn23i+CXxW/owM4WpDLFUbpjxe4NS3BhLVZQ5i7E0ZrxuQ/vwekIeciyamgB1UIYxxM6A== + dependencies: + "@babel/types" "^7.0.0" + +"@babel/helper-module-transforms@^7.1.0": + version "7.2.2" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.2.2.tgz#ab2f8e8d231409f8370c883d20c335190284b963" + integrity sha512-YRD7I6Wsv+IHuTPkAmAS4HhY0dkPobgLftHp0cRGZSdrRvmZY8rFvae/GVu3bD00qscuvK3WPHB3YdNpBXUqrA== + dependencies: + "@babel/helper-module-imports" "^7.0.0" + "@babel/helper-simple-access" "^7.1.0" + "@babel/helper-split-export-declaration" "^7.0.0" + "@babel/template" "^7.2.2" + "@babel/types" "^7.2.2" + lodash "^4.17.10" + +"@babel/helper-optimise-call-expression@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.0.0.tgz#a2920c5702b073c15de51106200aa8cad20497d5" + integrity sha512-u8nd9NQePYNQV8iPWu/pLLYBqZBa4ZaY1YWRFMuxrid94wKI1QNt67NEZ7GAe5Kc/0LLScbim05xZFWkAdrj9g== + dependencies: + "@babel/types" "^7.0.0" + +"@babel/helper-plugin-utils@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.0.0.tgz#bbb3fbee98661c569034237cc03967ba99b4f250" + integrity sha512-CYAOUCARwExnEixLdB6sDm2dIJ/YgEAKDM1MOeMeZu9Ld/bDgVo8aiWrXwcY7OBh+1Ea2uUcVRcxKk0GJvW7QA== + +"@babel/helper-regex@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/helper-regex/-/helper-regex-7.0.0.tgz#2c1718923b57f9bbe64705ffe5640ac64d9bdb27" + integrity sha512-TR0/N0NDCcUIUEbqV6dCO+LptmmSQFQ7q70lfcEB4URsjD0E1HzicrwUH+ap6BAQ2jhCX9Q4UqZy4wilujWlkg== + dependencies: + lodash "^4.17.10" + +"@babel/helper-remap-async-to-generator@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.1.0.tgz#361d80821b6f38da75bd3f0785ece20a88c5fe7f" + integrity sha512-3fOK0L+Fdlg8S5al8u/hWE6vhufGSn0bN09xm2LXMy//REAF8kDCrYoOBKYmA8m5Nom+sV9LyLCwrFynA8/slg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.0.0" + "@babel/helper-wrap-function" "^7.1.0" + "@babel/template" "^7.1.0" + "@babel/traverse" "^7.1.0" + "@babel/types" "^7.0.0" + +"@babel/helper-replace-supers@^7.1.0", "@babel/helper-replace-supers@^7.3.4": + version "7.3.4" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.3.4.tgz#a795208e9b911a6eeb08e5891faacf06e7013e13" + integrity sha512-pvObL9WVf2ADs+ePg0jrqlhHoxRXlOa+SHRHzAXIz2xkYuOHfGl+fKxPMaS4Fq+uje8JQPobnertBBvyrWnQ1A== + dependencies: + "@babel/helper-member-expression-to-functions" "^7.0.0" + "@babel/helper-optimise-call-expression" "^7.0.0" + "@babel/traverse" "^7.3.4" + "@babel/types" "^7.3.4" + +"@babel/helper-simple-access@^7.1.0": + version "7.1.0" + resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.1.0.tgz#65eeb954c8c245beaa4e859da6188f39d71e585c" + integrity sha512-Vk+78hNjRbsiu49zAPALxTb+JUQCz1aolpd8osOF16BGnLtseD21nbHgLPGUwrXEurZgiCOUmvs3ExTu4F5x6w== + dependencies: + "@babel/template" "^7.1.0" + "@babel/types" "^7.0.0" + +"@babel/helper-split-export-declaration@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.0.0.tgz#3aae285c0311c2ab095d997b8c9a94cad547d813" + integrity sha512-MXkOJqva62dfC0w85mEf/LucPPS/1+04nmmRMPEBUB++hiiThQ2zPtX/mEWQ3mtzCEjIJvPY8nuwxXtQeQwUag== + dependencies: + "@babel/types" "^7.0.0" + +"@babel/helper-wrap-function@^7.1.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.2.0.tgz#c4e0012445769e2815b55296ead43a958549f6fa" + integrity sha512-o9fP1BZLLSrYlxYEYyl2aS+Flun5gtjTIG8iln+XuEzQTs0PLagAGSXUcqruJwD5fM48jzIEggCKpIfWTcR7pQ== + dependencies: + "@babel/helper-function-name" "^7.1.0" + "@babel/template" "^7.1.0" + "@babel/traverse" "^7.1.0" + "@babel/types" "^7.2.0" + +"@babel/helpers@^7.2.0": + version "7.3.1" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.3.1.tgz#949eec9ea4b45d3210feb7dc1c22db664c9e44b9" + integrity sha512-Q82R3jKsVpUV99mgX50gOPCWwco9Ec5Iln/8Vyu4osNIOQgSrd9RFrQeUvmvddFNoLwMyOUWU+5ckioEKpDoGA== + dependencies: + "@babel/template" "^7.1.2" + "@babel/traverse" "^7.1.5" + "@babel/types" "^7.3.0" + +"@babel/highlight@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.0.0.tgz#f710c38c8d458e6dd9a201afb637fcb781ce99e4" + integrity sha512-UFMC4ZeFC48Tpvj7C8UgLvtkaUuovQX+5xNWrsIoMG8o2z+XFKjKaN9iVmS84dPwVN00W4wPmqvYoZF3EGAsfw== + dependencies: + chalk "^2.0.0" + esutils "^2.0.2" + js-tokens "^4.0.0" + +"@babel/parser@^7.2.2", "@babel/parser@^7.3.4": + version "7.3.4" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.3.4.tgz#a43357e4bbf4b92a437fb9e465c192848287f27c" + integrity sha512-tXZCqWtlOOP4wgCp6RjRvLmfuhnqTLy9VHwRochJBCP2nDm27JnnuFEnXFASVyQNHk36jD1tAammsCEEqgscIQ== + +"@babel/plugin-proposal-async-generator-functions@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.2.0.tgz#b289b306669dce4ad20b0252889a15768c9d417e" + integrity sha512-+Dfo/SCQqrwx48ptLVGLdE39YtWRuKc/Y9I5Fy0P1DDBB9lsAHpjcEJQt+4IifuSOSTLBKJObJqMvaO1pIE8LQ== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-remap-async-to-generator" "^7.1.0" + "@babel/plugin-syntax-async-generators" "^7.2.0" + +"@babel/plugin-proposal-json-strings@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.2.0.tgz#568ecc446c6148ae6b267f02551130891e29f317" + integrity sha512-MAFV1CA/YVmYwZG0fBQyXhmj0BHCB5egZHCKWIFVv/XCxAeVGIHfos3SwDck4LvCllENIAg7xMKOG5kH0dzyUg== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-syntax-json-strings" "^7.2.0" + +"@babel/plugin-proposal-object-rest-spread@^7.3.4": + version "7.3.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.3.4.tgz#47f73cf7f2a721aad5c0261205405c642e424654" + integrity sha512-j7VQmbbkA+qrzNqbKHrBsW3ddFnOeva6wzSe/zB7T+xaxGc+RCpwo44wCmRixAIGRoIpmVgvzFzNJqQcO3/9RA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-syntax-object-rest-spread" "^7.2.0" + +"@babel/plugin-proposal-optional-catch-binding@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.2.0.tgz#135d81edb68a081e55e56ec48541ece8065c38f5" + integrity sha512-mgYj3jCcxug6KUcX4OBoOJz3CMrwRfQELPQ5560F70YQUBZB7uac9fqaWamKR1iWUzGiK2t0ygzjTScZnVz75g== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-syntax-optional-catch-binding" "^7.2.0" + +"@babel/plugin-proposal-unicode-property-regex@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.2.0.tgz#abe7281fe46c95ddc143a65e5358647792039520" + integrity sha512-LvRVYb7kikuOtIoUeWTkOxQEV1kYvL5B6U3iWEGCzPNRus1MzJweFqORTj+0jkxozkTSYNJozPOddxmqdqsRpw== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-regex" "^7.0.0" + regexpu-core "^4.2.0" + +"@babel/plugin-syntax-async-generators@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.2.0.tgz#69e1f0db34c6f5a0cf7e2b3323bf159a76c8cb7f" + integrity sha512-1ZrIRBv2t0GSlcwVoQ6VgSLpLgiN/FVQUzt9znxo7v2Ov4jJrs8RY8tv0wvDmFN3qIdMKWrmMMW6yZ0G19MfGg== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-syntax-json-strings@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.2.0.tgz#72bd13f6ffe1d25938129d2a186b11fd62951470" + integrity sha512-5UGYnMSLRE1dqqZwug+1LISpA403HzlSfsg6P9VXU6TBjcSHeNlw4DxDx7LgpF+iKZoOG/+uzqoRHTdcUpiZNg== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-syntax-object-rest-spread@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.2.0.tgz#3b7a3e733510c57e820b9142a6579ac8b0dfad2e" + integrity sha512-t0JKGgqk2We+9may3t0xDdmneaXmyxq0xieYcKHxIsrJO64n1OiMWNUtc5gQK1PA0NpdCRrtZp4z+IUaKugrSA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-syntax-optional-catch-binding@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.2.0.tgz#a94013d6eda8908dfe6a477e7f9eda85656ecf5c" + integrity sha512-bDe4xKNhb0LI7IvZHiA13kff0KEfaGX/Hv4lMA9+7TEc63hMNvfKo6ZFpXhKuEp+II/q35Gc4NoMeDZyaUbj9w== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-arrow-functions@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.2.0.tgz#9aeafbe4d6ffc6563bf8f8372091628f00779550" + integrity sha512-ER77Cax1+8/8jCB9fo4Ud161OZzWN5qawi4GusDuRLcDbDG+bIGYY20zb2dfAFdTRGzrfq2xZPvF0R64EHnimg== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-async-to-generator@^7.3.4": + version "7.3.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.3.4.tgz#4e45408d3c3da231c0e7b823f407a53a7eb3048c" + integrity sha512-Y7nCzv2fw/jEZ9f678MuKdMo99MFDJMT/PvD9LisrR5JDFcJH6vYeH6RnjVt3p5tceyGRvTtEN0VOlU+rgHZjA== + dependencies: + "@babel/helper-module-imports" "^7.0.0" + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-remap-async-to-generator" "^7.1.0" + +"@babel/plugin-transform-block-scoped-functions@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.2.0.tgz#5d3cc11e8d5ddd752aa64c9148d0db6cb79fd190" + integrity sha512-ntQPR6q1/NKuphly49+QiQiTN0O63uOwjdD6dhIjSWBI5xlrbUFh720TIpzBhpnrLfv2tNH/BXvLIab1+BAI0w== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-block-scoping@^7.3.4": + version "7.3.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.3.4.tgz#5c22c339de234076eee96c8783b2fed61202c5c4" + integrity sha512-blRr2O8IOZLAOJklXLV4WhcEzpYafYQKSGT3+R26lWG41u/FODJuBggehtOwilVAcFu393v3OFj+HmaE6tVjhA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + lodash "^4.17.11" + +"@babel/plugin-transform-classes@^7.3.4": + version "7.3.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-classes/-/plugin-transform-classes-7.3.4.tgz#dc173cb999c6c5297e0b5f2277fdaaec3739d0cc" + integrity sha512-J9fAvCFBkXEvBimgYxCjvaVDzL6thk0j0dBvCeZmIUDBwyt+nv6HfbImsSrWsYXfDNDivyANgJlFXDUWRTZBuA== + dependencies: + "@babel/helper-annotate-as-pure" "^7.0.0" + "@babel/helper-define-map" "^7.1.0" + "@babel/helper-function-name" "^7.1.0" + "@babel/helper-optimise-call-expression" "^7.0.0" + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-replace-supers" "^7.3.4" + "@babel/helper-split-export-declaration" "^7.0.0" + globals "^11.1.0" + +"@babel/plugin-transform-computed-properties@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.2.0.tgz#83a7df6a658865b1c8f641d510c6f3af220216da" + integrity sha512-kP/drqTxY6Xt3NNpKiMomfgkNn4o7+vKxK2DDKcBG9sHj51vHqMBGy8wbDS/J4lMxnqs153/T3+DmCEAkC5cpA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-destructuring@^7.2.0": + version "7.3.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.3.2.tgz#f2f5520be055ba1c38c41c0e094d8a461dd78f2d" + integrity sha512-Lrj/u53Ufqxl/sGxyjsJ2XNtNuEjDyjpqdhMNh5aZ+XFOdThL46KBj27Uem4ggoezSYBxKWAil6Hu8HtwqesYw== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-dotall-regex@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.2.0.tgz#f0aabb93d120a8ac61e925ea0ba440812dbe0e49" + integrity sha512-sKxnyHfizweTgKZf7XsXu/CNupKhzijptfTM+bozonIuyVrLWVUvYjE2bhuSBML8VQeMxq4Mm63Q9qvcvUcciQ== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-regex" "^7.0.0" + regexpu-core "^4.1.3" + +"@babel/plugin-transform-duplicate-keys@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.2.0.tgz#d952c4930f312a4dbfff18f0b2914e60c35530b3" + integrity sha512-q+yuxW4DsTjNceUiTzK0L+AfQ0zD9rWaTLiUqHA8p0gxx7lu1EylenfzjeIWNkPy6e/0VG/Wjw9uf9LueQwLOw== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-exponentiation-operator@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.2.0.tgz#a63868289e5b4007f7054d46491af51435766008" + integrity sha512-umh4hR6N7mu4Elq9GG8TOu9M0bakvlsREEC+ialrQN6ABS4oDQ69qJv1VtR3uxlKMCQMCvzk7vr17RHKcjx68A== + dependencies: + "@babel/helper-builder-binary-assignment-operator-visitor" "^7.1.0" + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-for-of@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.2.0.tgz#ab7468befa80f764bb03d3cb5eef8cc998e1cad9" + integrity sha512-Kz7Mt0SsV2tQk6jG5bBv5phVbkd0gd27SgYD4hH1aLMJRchM0dzHaXvrWhVZ+WxAlDoAKZ7Uy3jVTW2mKXQ1WQ== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-function-name@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.2.0.tgz#f7930362829ff99a3174c39f0afcc024ef59731a" + integrity sha512-kWgksow9lHdvBC2Z4mxTsvc7YdY7w/V6B2vy9cTIPtLEE9NhwoWivaxdNM/S37elu5bqlLP/qOY906LukO9lkQ== + dependencies: + "@babel/helper-function-name" "^7.1.0" + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-literals@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-literals/-/plugin-transform-literals-7.2.0.tgz#690353e81f9267dad4fd8cfd77eafa86aba53ea1" + integrity sha512-2ThDhm4lI4oV7fVQ6pNNK+sx+c/GM5/SaML0w/r4ZB7sAneD/piDJtwdKlNckXeyGK7wlwg2E2w33C/Hh+VFCg== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-modules-amd@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.2.0.tgz#82a9bce45b95441f617a24011dc89d12da7f4ee6" + integrity sha512-mK2A8ucqz1qhrdqjS9VMIDfIvvT2thrEsIQzbaTdc5QFzhDjQv2CkJJ5f6BXIkgbmaoax3zBr2RyvV/8zeoUZw== + dependencies: + "@babel/helper-module-transforms" "^7.1.0" + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-modules-commonjs@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.2.0.tgz#c4f1933f5991d5145e9cfad1dfd848ea1727f404" + integrity sha512-V6y0uaUQrQPXUrmj+hgnks8va2L0zcZymeU7TtWEgdRLNkceafKXEduv7QzgQAE4lT+suwooG9dC7LFhdRAbVQ== + dependencies: + "@babel/helper-module-transforms" "^7.1.0" + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-simple-access" "^7.1.0" + +"@babel/plugin-transform-modules-systemjs@^7.3.4": + version "7.3.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.3.4.tgz#813b34cd9acb6ba70a84939f3680be0eb2e58861" + integrity sha512-VZ4+jlGOF36S7TjKs8g4ojp4MEI+ebCQZdswWb/T9I4X84j8OtFAyjXjt/M16iIm5RIZn0UMQgg/VgIwo/87vw== + dependencies: + "@babel/helper-hoist-variables" "^7.0.0" + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-modules-umd@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.2.0.tgz#7678ce75169f0877b8eb2235538c074268dd01ae" + integrity sha512-BV3bw6MyUH1iIsGhXlOK6sXhmSarZjtJ/vMiD9dNmpY8QXFFQTj+6v92pcfy1iqa8DeAfJFwoxcrS/TUZda6sw== + dependencies: + "@babel/helper-module-transforms" "^7.1.0" + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-named-capturing-groups-regex@^7.3.0": + version "7.3.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.3.0.tgz#140b52985b2d6ef0cb092ef3b29502b990f9cd50" + integrity sha512-NxIoNVhk9ZxS+9lSoAQ/LM0V2UEvARLttEHUrRDGKFaAxOYQcrkN/nLRE+BbbicCAvZPl7wMP0X60HsHE5DtQw== + dependencies: + regexp-tree "^0.1.0" + +"@babel/plugin-transform-new-target@^7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.0.0.tgz#ae8fbd89517fa7892d20e6564e641e8770c3aa4a" + integrity sha512-yin069FYjah+LbqfGeTfzIBODex/e++Yfa0rH0fpfam9uTbuEeEOx5GLGr210ggOV77mVRNoeqSYqeuaqSzVSw== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-object-super@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.2.0.tgz#b35d4c10f56bab5d650047dad0f1d8e8814b6598" + integrity sha512-VMyhPYZISFZAqAPVkiYb7dUe2AsVi2/wCT5+wZdsNO31FojQJa9ns40hzZ6U9f50Jlq4w6qwzdBB2uwqZ00ebg== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-replace-supers" "^7.1.0" + +"@babel/plugin-transform-parameters@^7.2.0": + version "7.3.3" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.3.3.tgz#3a873e07114e1a5bee17d04815662c8317f10e30" + integrity sha512-IrIP25VvXWu/VlBWTpsjGptpomtIkYrN/3aDp4UKm7xK6UxZY88kcJ1UwETbzHAlwN21MnNfwlar0u8y3KpiXw== + dependencies: + "@babel/helper-call-delegate" "^7.1.0" + "@babel/helper-get-function-arity" "^7.0.0" + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-regenerator@^7.3.4": + version "7.3.4" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.3.4.tgz#1601655c362f5b38eead6a52631f5106b29fa46a" + integrity sha512-hvJg8EReQvXT6G9H2MvNPXkv9zK36Vxa1+csAVTpE1J3j0zlHplw76uudEbJxgvqZzAq9Yh45FLD4pk5mKRFQA== + dependencies: + regenerator-transform "^0.13.4" + +"@babel/plugin-transform-shorthand-properties@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.2.0.tgz#6333aee2f8d6ee7e28615457298934a3b46198f0" + integrity sha512-QP4eUM83ha9zmYtpbnyjTLAGKQritA5XW/iG9cjtuOI8s1RuL/3V6a3DeSHfKutJQ+ayUfeZJPcnCYEQzaPQqg== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-spread@^7.2.0": + version "7.2.2" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-spread/-/plugin-transform-spread-7.2.2.tgz#3103a9abe22f742b6d406ecd3cd49b774919b406" + integrity sha512-KWfky/58vubwtS0hLqEnrWJjsMGaOeSBn90Ezn5Jeg9Z8KKHmELbP1yGylMlm5N6TPKeY9A2+UaSYLdxahg01w== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-sticky-regex@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.2.0.tgz#a1e454b5995560a9c1e0d537dfc15061fd2687e1" + integrity sha512-KKYCoGaRAf+ckH8gEL3JHUaFVyNHKe3ASNsZ+AlktgHevvxGigoIttrEJb8iKN03Q7Eazlv1s6cx2B2cQ3Jabw== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-regex" "^7.0.0" + +"@babel/plugin-transform-template-literals@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.2.0.tgz#d87ed01b8eaac7a92473f608c97c089de2ba1e5b" + integrity sha512-FkPix00J9A/XWXv4VoKJBMeSkyY9x/TqIh76wzcdfl57RJJcf8CehQ08uwfhCDNtRQYtHQKBTwKZDEyjE13Lwg== + dependencies: + "@babel/helper-annotate-as-pure" "^7.0.0" + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-typeof-symbol@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.2.0.tgz#117d2bcec2fbf64b4b59d1f9819894682d29f2b2" + integrity sha512-2LNhETWYxiYysBtrBTqL8+La0jIoQQnIScUJc74OYvUGRmkskNY4EzLCnjHBzdmb38wqtTaixpo1NctEcvMDZw== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + +"@babel/plugin-transform-unicode-regex@^7.2.0": + version "7.2.0" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.2.0.tgz#4eb8db16f972f8abb5062c161b8b115546ade08b" + integrity sha512-m48Y0lMhrbXEJnVUaYly29jRXbQ3ksxPrS1Tg8t+MHqzXhtBYAvI51euOBaoAlZLPHsieY9XPVMf80a5x0cPcA== + dependencies: + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/helper-regex" "^7.0.0" + regexpu-core "^4.1.3" + +"@babel/preset-env@^7.0.0": + version "7.3.4" + resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.3.4.tgz#887cf38b6d23c82f19b5135298bdb160062e33e1" + integrity sha512-2mwqfYMK8weA0g0uBKOt4FE3iEodiHy9/CW0b+nWXcbL+pGzLx8ESYc+j9IIxr6LTDHWKgPm71i9smo02bw+gA== + dependencies: + "@babel/helper-module-imports" "^7.0.0" + "@babel/helper-plugin-utils" "^7.0.0" + "@babel/plugin-proposal-async-generator-functions" "^7.2.0" + "@babel/plugin-proposal-json-strings" "^7.2.0" + "@babel/plugin-proposal-object-rest-spread" "^7.3.4" + "@babel/plugin-proposal-optional-catch-binding" "^7.2.0" + "@babel/plugin-proposal-unicode-property-regex" "^7.2.0" + "@babel/plugin-syntax-async-generators" "^7.2.0" + "@babel/plugin-syntax-json-strings" "^7.2.0" + "@babel/plugin-syntax-object-rest-spread" "^7.2.0" + "@babel/plugin-syntax-optional-catch-binding" "^7.2.0" + "@babel/plugin-transform-arrow-functions" "^7.2.0" + "@babel/plugin-transform-async-to-generator" "^7.3.4" + "@babel/plugin-transform-block-scoped-functions" "^7.2.0" + "@babel/plugin-transform-block-scoping" "^7.3.4" + "@babel/plugin-transform-classes" "^7.3.4" + "@babel/plugin-transform-computed-properties" "^7.2.0" + "@babel/plugin-transform-destructuring" "^7.2.0" + "@babel/plugin-transform-dotall-regex" "^7.2.0" + "@babel/plugin-transform-duplicate-keys" "^7.2.0" + "@babel/plugin-transform-exponentiation-operator" "^7.2.0" + "@babel/plugin-transform-for-of" "^7.2.0" + "@babel/plugin-transform-function-name" "^7.2.0" + "@babel/plugin-transform-literals" "^7.2.0" + "@babel/plugin-transform-modules-amd" "^7.2.0" + "@babel/plugin-transform-modules-commonjs" "^7.2.0" + "@babel/plugin-transform-modules-systemjs" "^7.3.4" + "@babel/plugin-transform-modules-umd" "^7.2.0" + "@babel/plugin-transform-named-capturing-groups-regex" "^7.3.0" + "@babel/plugin-transform-new-target" "^7.0.0" + "@babel/plugin-transform-object-super" "^7.2.0" + "@babel/plugin-transform-parameters" "^7.2.0" + "@babel/plugin-transform-regenerator" "^7.3.4" + "@babel/plugin-transform-shorthand-properties" "^7.2.0" + "@babel/plugin-transform-spread" "^7.2.0" + "@babel/plugin-transform-sticky-regex" "^7.2.0" + "@babel/plugin-transform-template-literals" "^7.2.0" + "@babel/plugin-transform-typeof-symbol" "^7.2.0" + "@babel/plugin-transform-unicode-regex" "^7.2.0" + browserslist "^4.3.4" + invariant "^2.2.2" + js-levenshtein "^1.1.3" + semver "^5.3.0" + +"@babel/template@^7.1.0", "@babel/template@^7.1.2", "@babel/template@^7.2.2": + version "7.2.2" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.2.2.tgz#005b3fdf0ed96e88041330379e0da9a708eb2907" + integrity sha512-zRL0IMM02AUDwghf5LMSSDEz7sBCO2YnNmpg3uWTZj/v1rcG2BmQUvaGU8GhU8BvfMh1k2KIAYZ7Ji9KXPUg7g== + dependencies: + "@babel/code-frame" "^7.0.0" + "@babel/parser" "^7.2.2" + "@babel/types" "^7.2.2" + +"@babel/traverse@^7.1.0", "@babel/traverse@^7.1.5", "@babel/traverse@^7.3.4": + version "7.3.4" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.3.4.tgz#1330aab72234f8dea091b08c4f8b9d05c7119e06" + integrity sha512-TvTHKp6471OYEcE/91uWmhR6PrrYywQntCHSaZ8CM8Vmp+pjAusal4nGB2WCCQd0rvI7nOMKn9GnbcvTUz3/ZQ== + dependencies: + "@babel/code-frame" "^7.0.0" + "@babel/generator" "^7.3.4" + "@babel/helper-function-name" "^7.1.0" + "@babel/helper-split-export-declaration" "^7.0.0" + "@babel/parser" "^7.3.4" + "@babel/types" "^7.3.4" + debug "^4.1.0" + globals "^11.1.0" + lodash "^4.17.11" + +"@babel/types@^7.0.0", "@babel/types@^7.2.0", "@babel/types@^7.2.2", "@babel/types@^7.3.0", "@babel/types@^7.3.4": + version "7.3.4" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.3.4.tgz#bf482eaeaffb367a28abbf9357a94963235d90ed" + integrity sha512-WEkp8MsLftM7O/ty580wAmZzN1nDmCACc5+jFzUt+GUFNNIi3LdRlueYz0YIlmJhlZx1QYDMZL5vdWCL0fNjFQ== + dependencies: + esutils "^2.0.2" + lodash "^4.17.11" + to-fast-properties "^2.0.0" + "@cypress/listr-verbose-renderer@0.4.1": version "0.4.1" resolved "https://registry.yarnpkg.com/@cypress/listr-verbose-renderer/-/listr-verbose-renderer-0.4.1.tgz#a77492f4b11dcc7c446a34b3e28721afd33c642a" + integrity sha1-p3SS9LEdzHxEajSz4ochr9M8ZCo= dependencies: chalk "^1.1.3" cli-cursor "^1.0.2" date-fns "^1.27.2" figures "^1.7.0" -"@cypress/webpack-preprocessor@^2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@cypress/webpack-preprocessor/-/webpack-preprocessor-2.0.1.tgz#9d21c828f49211b880ea08f306b3443b78c38984" +"@cypress/webpack-preprocessor@^4.0.3": + version "4.0.3" + resolved "https://registry.yarnpkg.com/@cypress/webpack-preprocessor/-/webpack-preprocessor-4.0.3.tgz#9f1029a4293f261c5a4a6e1e764a1a4ae6a09a66" + integrity sha512-gw6QNif0UaMW1FDl5tej14isvDWbONib9t1iXGlWUaz0/pEdIvp6ik7mnOaph/IixkQXtmeOJ8CWj+995Pj47w== dependencies: - babel-core "6.26.0" - babel-loader "7.1.4" - babel-preset-env "1.6.0" - babel-preset-react "6.24.1" bluebird "3.5.0" debug "3.1.0" lodash.clonedeep "4.5.0" - webpack "^4.0.0" + optionalDependencies: + "@babel/core" "^7.0.1" + "@babel/preset-env" "^7.0.0" + babel-loader "^8.0.2" "@cypress/xvfb@1.2.3": version "1.2.3" resolved "https://registry.yarnpkg.com/@cypress/xvfb/-/xvfb-1.2.3.tgz#6319afdcdcff7d1505daeeaa84484d0596189860" + integrity sha512-yYrK+/bgL3hwoRHMZG4r5fyLniCy1pXex5fimtewAY6vE/jsVs8Q37UsEO03tFlcmiLnQ3rBNMaZBYTi/+C1cw== dependencies: debug "^3.1.0" lodash.once "^4.1.1" @@ -34,250 +626,266 @@ "@types/blob-util@1.3.3": version "1.3.3" resolved "https://registry.yarnpkg.com/@types/blob-util/-/blob-util-1.3.3.tgz#adba644ae34f88e1dd9a5864c66ad651caaf628a" + integrity sha512-4ahcL/QDnpjWA2Qs16ZMQif7HjGP2cw3AGjHabybjw7Vm1EKu+cfQN1D78BaZbS1WJNa1opSMF5HNMztx7lR0w== "@types/bluebird@3.5.18": version "3.5.18" resolved "https://registry.yarnpkg.com/@types/bluebird/-/bluebird-3.5.18.tgz#6a60435d4663e290f3709898a4f75014f279c4d6" - -"@types/bn.js@*": - version "4.11.1" - resolved "https://registry.yarnpkg.com/@types/bn.js/-/bn.js-4.11.1.tgz#6fd07b93490ecf0f3501a31ea9cfd330885b10fa" - dependencies: - "@types/node" "*" + integrity sha512-OTPWHmsyW18BhrnG5x8F7PzeZ2nFxmHGb42bZn79P9hl+GI5cMzyPgQTwNjbem0lJhoru/8vtjAFCUOu3+gE2w== "@types/chai-jquery@1.1.35": version "1.1.35" resolved "https://registry.yarnpkg.com/@types/chai-jquery/-/chai-jquery-1.1.35.tgz#9a8f0a39ec0851b2768a8f8c764158c2a2568d04" + integrity sha512-7aIt9QMRdxuagLLI48dPz96YJdhu64p6FCa6n4qkGN5DQLHnrIjZpD9bXCvV2G0NwgZ1FAmfP214dxc5zNCfgQ== dependencies: "@types/chai" "*" "@types/jquery" "*" "@types/chai@*": - version "4.1.4" - resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.1.4.tgz#5ca073b330d90b4066d6ce18f60d57f2084ce8ca" + version "4.1.7" + resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.1.7.tgz#1b8e33b61a8c09cbe1f85133071baa0dbf9fa71a" + integrity sha512-2Y8uPt0/jwjhQ6EiluT0XCri1Dbplr0ZxfFXUz+ye13gaqE8u5gL5ppao1JrUYr9cIip5S6MvQzBS7Kke7U9VA== "@types/chai@4.0.8": version "4.0.8" resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.0.8.tgz#d27600e9ba2f371e08695d90a0fe0408d89c7be7" + integrity sha512-m812CONwdZn/dMzkIJEY0yAs4apyTkTORgfB2UsMOxgkUbC205AHnm4T8I0I5gPg9MHrFc1dJ35iS75c0CJkjg== "@types/jquery@*": + version "3.3.29" + resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-3.3.29.tgz#680a2219ce3c9250483722fccf5570d1e2d08abd" + integrity sha512-FhJvBninYD36v3k6c+bVk1DSZwh7B5Dpb/Pyk3HKVsiohn0nhbefZZ+3JXbWQhFyt0MxSl2jRDdGQPHeOHFXrQ== + dependencies: + "@types/sizzle" "*" + +"@types/jquery@3.3.6": version "3.3.6" resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-3.3.6.tgz#5932ead926307ca21e5b36808257f7c926b06565" - -"@types/jquery@3.2.16": - version "3.2.16" - resolved "https://registry.yarnpkg.com/@types/jquery/-/jquery-3.2.16.tgz#04419c404a3194350e7d3f339a90e72c88db3111" + integrity sha512-403D4wN95Mtzt2EoQHARf5oe/jEPhzBOBNrunk+ydQGW8WmkQ/E8rViRAEB1qEt/vssfGfNVD6ujP4FVeegrLg== "@types/lodash@4.14.87": version "4.14.87" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.87.tgz#55f92183b048c2c64402afe472f8333f4e319a6b" + integrity sha512-AqRC+aEF4N0LuNHtcjKtvF9OTfqZI0iaBoe3dA6m/W+/YZJBZjBmW/QIZ8fBeXC6cnytSY9tBoFBqZ9uSCeVsw== "@types/minimatch@3.0.3": version "3.0.3" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" + integrity sha512-tHq6qdbT9U1IRSGf14CL0pUlULksvY9OZ+5eEgl1N7t+OA3tGvNpxJCzuKQlsNgCVwbAs670L1vcVQi8j9HjnA== "@types/mocha@2.2.44": version "2.2.44" resolved "https://registry.yarnpkg.com/@types/mocha/-/mocha-2.2.44.tgz#1d4a798e53f35212fd5ad4d04050620171cd5b5e" + integrity sha512-k2tWTQU8G4+iSMvqKi0Q9IIsWAp/n8xzdZS4Q4YVIltApoMA00wFBFdlJnmoaK1/z7B0Cy0yPe6GgXteSmdUNw== -"@types/node@*", "@types/node@^10.3.2": - version "10.9.4" - resolved "https://registry.yarnpkg.com/@types/node/-/node-10.9.4.tgz#0f4cb2dc7c1de6096055357f70179043c33e9897" - -"@types/sinon-chai@2.7.29": - version "2.7.29" - resolved "https://registry.yarnpkg.com/@types/sinon-chai/-/sinon-chai-2.7.29.tgz#4db01497e2dd1908b2bd30d1782f456353f5f723" +"@types/sinon-chai@3.2.2": + version "3.2.2" + resolved "https://registry.yarnpkg.com/@types/sinon-chai/-/sinon-chai-3.2.2.tgz#5cfdbda70bae30f79a9423334af9e490e4cce793" + integrity sha512-5zSs2AslzyPZdOsbm2NRtuSNAI2aTWzNKOHa/GRecKo7a5efYD7qGcPxMZXQDayVXT2Vnd5waXxBvV31eCZqiA== dependencies: "@types/chai" "*" "@types/sinon" "*" "@types/sinon@*": - version "5.0.2" - resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-5.0.2.tgz#7a9d64df40b26dd48d673f745addc2fdb2bda193" + version "7.0.9" + resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-7.0.9.tgz#981593ea396a4fb1b69e3d8ba04de0a420fd4b16" + integrity sha512-PS102Fj4BAa9B8C0qLp677UiW9sdI+9ZhsD6GdPbcNKNdymyKKRJ1Bsc5Uk5pQV7jTtLozTQdxzgnQx+aH8Pvg== -"@types/sinon@4.0.0": - version "4.0.0" - resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-4.0.0.tgz#9a93ffa4ee1329e85166278a5ed99f81dc4c8362" +"@types/sinon@7.0.0": + version "7.0.0" + resolved "https://registry.yarnpkg.com/@types/sinon/-/sinon-7.0.0.tgz#84e707e157ec17d3e4c2a137f41fc3f416c0551e" + integrity sha512-kcYoPw0uKioFVC/oOqafk2yizSceIQXCYnkYts9vJIwQklFRsMubTObTDrjQamUyBRd47332s85074cd/hCwxg== -"@types/underscore@*": - version "1.8.9" - resolved "https://registry.yarnpkg.com/@types/underscore/-/underscore-1.8.9.tgz#fef41f800cd23db1b4f262ddefe49cd952d82323" +"@types/sizzle@*": + version "2.3.2" + resolved "https://registry.yarnpkg.com/@types/sizzle/-/sizzle-2.3.2.tgz#a811b8c18e2babab7d542b3365887ae2e4d9de47" + integrity sha512-7EJYyKTL7tFR8+gDbB6Wwz/arpGa0Mywk1TJbNzKzHtzbwVmY4HR9WqS5VV7dsBUKQmPNr192jHr/VpBluj/hg== -"@types/web3@^1.0.3": - version "1.0.3" - resolved "https://registry.yarnpkg.com/@types/web3/-/web3-1.0.3.tgz#2c5f6905d46eb6e40da8eb7c1e553463862fa599" +"@webassemblyjs/ast@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.8.5.tgz#51b1c5fe6576a34953bf4b253df9f0d490d9e359" + integrity sha512-aJMfngIZ65+t71C3y2nBBg5FFG0Okt9m0XEgWZ7Ywgn1oMAT8cNwx00Uv1cQyHtidq0Xn94R4TAywO+LCQ+ZAQ== dependencies: - "@types/bn.js" "*" - "@types/underscore" "*" + "@webassemblyjs/helper-module-context" "1.8.5" + "@webassemblyjs/helper-wasm-bytecode" "1.8.5" + "@webassemblyjs/wast-parser" "1.8.5" -"@webassemblyjs/ast@1.5.13": - version "1.5.13" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.5.13.tgz#81155a570bd5803a30ec31436bc2c9c0ede38f25" +"@webassemblyjs/floating-point-hex-parser@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.8.5.tgz#1ba926a2923613edce496fd5b02e8ce8a5f49721" + integrity sha512-9p+79WHru1oqBh9ewP9zW95E3XAo+90oth7S5Re3eQnECGq59ly1Ri5tsIipKGpiStHsUYmY3zMLqtk3gTcOtQ== + +"@webassemblyjs/helper-api-error@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.8.5.tgz#c49dad22f645227c5edb610bdb9697f1aab721f7" + integrity sha512-Za/tnzsvnqdaSPOUXHyKJ2XI7PDX64kWtURyGiJJZKVEdFOsdKUCPTNEVFZq3zJ2R0G5wc2PZ5gvdTRFgm81zA== + +"@webassemblyjs/helper-buffer@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.8.5.tgz#fea93e429863dd5e4338555f42292385a653f204" + integrity sha512-Ri2R8nOS0U6G49Q86goFIPNgjyl6+oE1abW1pS84BuhP1Qcr5JqMwRFT3Ah3ADDDYGEgGs1iyb1DGX+kAi/c/Q== + +"@webassemblyjs/helper-code-frame@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.8.5.tgz#9a740ff48e3faa3022b1dff54423df9aa293c25e" + integrity sha512-VQAadSubZIhNpH46IR3yWO4kZZjMxN1opDrzePLdVKAZ+DFjkGD/rf4v1jap744uPVU6yjL/smZbRIIJTOUnKQ== dependencies: - "@webassemblyjs/helper-module-context" "1.5.13" - "@webassemblyjs/helper-wasm-bytecode" "1.5.13" - "@webassemblyjs/wast-parser" "1.5.13" - debug "^3.1.0" + "@webassemblyjs/wast-printer" "1.8.5" + +"@webassemblyjs/helper-fsm@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-fsm/-/helper-fsm-1.8.5.tgz#ba0b7d3b3f7e4733da6059c9332275d860702452" + integrity sha512-kRuX/saORcg8se/ft6Q2UbRpZwP4y7YrWsLXPbbmtepKr22i8Z4O3V5QE9DbZK908dh5Xya4Un57SDIKwB9eow== + +"@webassemblyjs/helper-module-context@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-module-context/-/helper-module-context-1.8.5.tgz#def4b9927b0101dc8cbbd8d1edb5b7b9c82eb245" + integrity sha512-/O1B236mN7UNEU4t9X7Pj38i4VoU8CcMHyy3l2cV/kIF4U5KoHXDVqcDuOs1ltkac90IM4vZdHc52t1x8Yfs3g== + dependencies: + "@webassemblyjs/ast" "1.8.5" mamacro "^0.0.3" -"@webassemblyjs/floating-point-hex-parser@1.5.13": - version "1.5.13" - resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.5.13.tgz#29ce0baa97411f70e8cce68ce9c0f9d819a4e298" +"@webassemblyjs/helper-wasm-bytecode@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.8.5.tgz#537a750eddf5c1e932f3744206551c91c1b93e61" + integrity sha512-Cu4YMYG3Ddl72CbmpjU/wbP6SACcOPVbHN1dI4VJNJVgFwaKf1ppeFJrwydOG3NDHxVGuCfPlLZNyEdIYlQ6QQ== -"@webassemblyjs/helper-api-error@1.5.13": - version "1.5.13" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.5.13.tgz#e49b051d67ee19a56e29b9aa8bd949b5b4442a59" - -"@webassemblyjs/helper-buffer@1.5.13": - version "1.5.13" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.5.13.tgz#873bb0a1b46449231137c1262ddfd05695195a1e" +"@webassemblyjs/helper-wasm-section@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.8.5.tgz#74ca6a6bcbe19e50a3b6b462847e69503e6bfcbf" + integrity sha512-VV083zwR+VTrIWWtgIUpqfvVdK4ff38loRmrdDBgBT8ADXYsEZ5mPQ4Nde90N3UYatHdYoDIFb7oHzMncI02tA== dependencies: - debug "^3.1.0" + "@webassemblyjs/ast" "1.8.5" + "@webassemblyjs/helper-buffer" "1.8.5" + "@webassemblyjs/helper-wasm-bytecode" "1.8.5" + "@webassemblyjs/wasm-gen" "1.8.5" -"@webassemblyjs/helper-code-frame@1.5.13": - version "1.5.13" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-code-frame/-/helper-code-frame-1.5.13.tgz#1bd2181b6a0be14e004f0fe9f5a660d265362b58" +"@webassemblyjs/ieee754@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.8.5.tgz#712329dbef240f36bf57bd2f7b8fb9bf4154421e" + integrity sha512-aaCvQYrvKbY/n6wKHb/ylAJr27GglahUO89CcGXMItrOBqRarUMxWLJgxm9PJNuKULwN5n1csT9bYoMeZOGF3g== dependencies: - "@webassemblyjs/wast-printer" "1.5.13" + "@xtuc/ieee754" "^1.2.0" -"@webassemblyjs/helper-fsm@1.5.13": - version "1.5.13" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-fsm/-/helper-fsm-1.5.13.tgz#cdf3d9d33005d543a5c5e5adaabf679ffa8db924" - -"@webassemblyjs/helper-module-context@1.5.13": - version "1.5.13" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-module-context/-/helper-module-context-1.5.13.tgz#dc29ddfb51ed657655286f94a5d72d8a489147c5" +"@webassemblyjs/leb128@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.8.5.tgz#044edeb34ea679f3e04cd4fd9824d5e35767ae10" + integrity sha512-plYUuUwleLIziknvlP8VpTgO4kqNaH57Y3JnNa6DLpu/sGcP6hbVdfdX5aHAV716pQBKrfuU26BJK29qY37J7A== dependencies: - debug "^3.1.0" - mamacro "^0.0.3" + "@xtuc/long" "4.2.2" -"@webassemblyjs/helper-wasm-bytecode@1.5.13": - version "1.5.13" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.5.13.tgz#03245817f0a762382e61733146f5773def15a747" +"@webassemblyjs/utf8@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.8.5.tgz#a8bf3b5d8ffe986c7c1e373ccbdc2a0915f0cedc" + integrity sha512-U7zgftmQriw37tfD934UNInokz6yTmn29inT2cAetAsaU9YeVCveWEwhKL1Mg4yS7q//NGdzy79nlXh3bT8Kjw== -"@webassemblyjs/helper-wasm-section@1.5.13": - version "1.5.13" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.5.13.tgz#efc76f44a10d3073b584b43c38a179df173d5c7d" +"@webassemblyjs/wasm-edit@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.8.5.tgz#962da12aa5acc1c131c81c4232991c82ce56e01a" + integrity sha512-A41EMy8MWw5yvqj7MQzkDjU29K7UJq1VrX2vWLzfpRHt3ISftOXqrtojn7nlPsZ9Ijhp5NwuODuycSvfAO/26Q== dependencies: - "@webassemblyjs/ast" "1.5.13" - "@webassemblyjs/helper-buffer" "1.5.13" - "@webassemblyjs/helper-wasm-bytecode" "1.5.13" - "@webassemblyjs/wasm-gen" "1.5.13" - debug "^3.1.0" + "@webassemblyjs/ast" "1.8.5" + "@webassemblyjs/helper-buffer" "1.8.5" + "@webassemblyjs/helper-wasm-bytecode" "1.8.5" + "@webassemblyjs/helper-wasm-section" "1.8.5" + "@webassemblyjs/wasm-gen" "1.8.5" + "@webassemblyjs/wasm-opt" "1.8.5" + "@webassemblyjs/wasm-parser" "1.8.5" + "@webassemblyjs/wast-printer" "1.8.5" -"@webassemblyjs/ieee754@1.5.13": - version "1.5.13" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.5.13.tgz#573e97c8c12e4eebb316ca5fde0203ddd90b0364" +"@webassemblyjs/wasm-gen@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.8.5.tgz#54840766c2c1002eb64ed1abe720aded714f98bc" + integrity sha512-BCZBT0LURC0CXDzj5FXSc2FPTsxwp3nWcqXQdOZE4U7h7i8FqtFK5Egia6f9raQLpEKT1VL7zr4r3+QX6zArWg== dependencies: - ieee754 "^1.1.11" + "@webassemblyjs/ast" "1.8.5" + "@webassemblyjs/helper-wasm-bytecode" "1.8.5" + "@webassemblyjs/ieee754" "1.8.5" + "@webassemblyjs/leb128" "1.8.5" + "@webassemblyjs/utf8" "1.8.5" -"@webassemblyjs/leb128@1.5.13": - version "1.5.13" - resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.5.13.tgz#ab52ebab9cec283c1c1897ac1da833a04a3f4cee" +"@webassemblyjs/wasm-opt@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.8.5.tgz#b24d9f6ba50394af1349f510afa8ffcb8a63d264" + integrity sha512-HKo2mO/Uh9A6ojzu7cjslGaHaUU14LdLbGEKqTR7PBKwT6LdPtLLh9fPY33rmr5wcOMrsWDbbdCHq4hQUdd37Q== dependencies: - long "4.0.0" + "@webassemblyjs/ast" "1.8.5" + "@webassemblyjs/helper-buffer" "1.8.5" + "@webassemblyjs/wasm-gen" "1.8.5" + "@webassemblyjs/wasm-parser" "1.8.5" -"@webassemblyjs/utf8@1.5.13": - version "1.5.13" - resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.5.13.tgz#6b53d2cd861cf94fa99c1f12779dde692fbc2469" - -"@webassemblyjs/wasm-edit@1.5.13": - version "1.5.13" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.5.13.tgz#c9cef5664c245cf11b3b3a73110c9155831724a8" +"@webassemblyjs/wasm-parser@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.8.5.tgz#21576f0ec88b91427357b8536383668ef7c66b8d" + integrity sha512-pi0SYE9T6tfcMkthwcgCpL0cM9nRYr6/6fjgDtL6q/ZqKHdMWvxitRi5JcZ7RI4SNJJYnYNaWy5UUrHQy998lw== dependencies: - "@webassemblyjs/ast" "1.5.13" - "@webassemblyjs/helper-buffer" "1.5.13" - "@webassemblyjs/helper-wasm-bytecode" "1.5.13" - "@webassemblyjs/helper-wasm-section" "1.5.13" - "@webassemblyjs/wasm-gen" "1.5.13" - "@webassemblyjs/wasm-opt" "1.5.13" - "@webassemblyjs/wasm-parser" "1.5.13" - "@webassemblyjs/wast-printer" "1.5.13" - debug "^3.1.0" + "@webassemblyjs/ast" "1.8.5" + "@webassemblyjs/helper-api-error" "1.8.5" + "@webassemblyjs/helper-wasm-bytecode" "1.8.5" + "@webassemblyjs/ieee754" "1.8.5" + "@webassemblyjs/leb128" "1.8.5" + "@webassemblyjs/utf8" "1.8.5" -"@webassemblyjs/wasm-gen@1.5.13": - version "1.5.13" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.5.13.tgz#8e6ea113c4b432fa66540189e79b16d7a140700e" +"@webassemblyjs/wast-parser@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-parser/-/wast-parser-1.8.5.tgz#e10eecd542d0e7bd394f6827c49f3df6d4eefb8c" + integrity sha512-daXC1FyKWHF1i11obK086QRlsMsY4+tIOKgBqI1lxAnkp9xe9YMcgOxm9kLe+ttjs5aWV2KKE1TWJCN57/Btsg== dependencies: - "@webassemblyjs/ast" "1.5.13" - "@webassemblyjs/helper-wasm-bytecode" "1.5.13" - "@webassemblyjs/ieee754" "1.5.13" - "@webassemblyjs/leb128" "1.5.13" - "@webassemblyjs/utf8" "1.5.13" + "@webassemblyjs/ast" "1.8.5" + "@webassemblyjs/floating-point-hex-parser" "1.8.5" + "@webassemblyjs/helper-api-error" "1.8.5" + "@webassemblyjs/helper-code-frame" "1.8.5" + "@webassemblyjs/helper-fsm" "1.8.5" + "@xtuc/long" "4.2.2" -"@webassemblyjs/wasm-opt@1.5.13": - version "1.5.13" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.5.13.tgz#147aad7717a7ee4211c36b21a5f4c30dddf33138" +"@webassemblyjs/wast-printer@1.8.5": + version "1.8.5" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.8.5.tgz#114bbc481fd10ca0e23b3560fa812748b0bae5bc" + integrity sha512-w0U0pD4EhlnvRyeJzBqaVSJAo9w/ce7/WPogeXLzGkO6hzhr4GnQIZ4W4uUt5b9ooAaXPtnXlj0gzsXEOUNYMg== dependencies: - "@webassemblyjs/ast" "1.5.13" - "@webassemblyjs/helper-buffer" "1.5.13" - "@webassemblyjs/wasm-gen" "1.5.13" - "@webassemblyjs/wasm-parser" "1.5.13" - debug "^3.1.0" + "@webassemblyjs/ast" "1.8.5" + "@webassemblyjs/wast-parser" "1.8.5" + "@xtuc/long" "4.2.2" -"@webassemblyjs/wasm-parser@1.5.13": - version "1.5.13" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.5.13.tgz#6f46516c5bb23904fbdf58009233c2dd8a54c72f" - dependencies: - "@webassemblyjs/ast" "1.5.13" - "@webassemblyjs/helper-api-error" "1.5.13" - "@webassemblyjs/helper-wasm-bytecode" "1.5.13" - "@webassemblyjs/ieee754" "1.5.13" - "@webassemblyjs/leb128" "1.5.13" - "@webassemblyjs/utf8" "1.5.13" +"@xtuc/ieee754@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@xtuc/ieee754/-/ieee754-1.2.0.tgz#eef014a3145ae477a1cbc00cd1e552336dceb790" + integrity sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA== -"@webassemblyjs/wast-parser@1.5.13": - version "1.5.13" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-parser/-/wast-parser-1.5.13.tgz#5727a705d397ae6a3ae99d7f5460acf2ec646eea" - dependencies: - "@webassemblyjs/ast" "1.5.13" - "@webassemblyjs/floating-point-hex-parser" "1.5.13" - "@webassemblyjs/helper-api-error" "1.5.13" - "@webassemblyjs/helper-code-frame" "1.5.13" - "@webassemblyjs/helper-fsm" "1.5.13" - long "^3.2.0" - mamacro "^0.0.3" - -"@webassemblyjs/wast-printer@1.5.13": - version "1.5.13" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.5.13.tgz#bb34d528c14b4f579e7ec11e793ec50ad7cd7c95" - dependencies: - "@webassemblyjs/ast" "1.5.13" - "@webassemblyjs/wast-parser" "1.5.13" - long "^3.2.0" +"@xtuc/long@4.2.2": + version "4.2.2" + resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" + integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== abbrev@1: version "1.1.1" resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.1.tgz#f8f2c887ad10bf67f634f005b6987fed3179aac8" + integrity sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q== -accepts@~1.3.5: - version "1.3.5" - resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.5.tgz#eb777df6011723a3b14e8a72c0805c8e86746bd2" - dependencies: - mime-types "~2.1.18" - negotiator "0.6.1" +acorn-dynamic-import@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/acorn-dynamic-import/-/acorn-dynamic-import-4.0.0.tgz#482210140582a36b83c3e342e1cfebcaa9240948" + integrity sha512-d3OEjQV4ROpoflsnUA8HozoIR504TFxNivYEUi6uwz0IYhBkTDXGuWlNdMtybRt3nqVx/L6XqMt0FxkXuWKZhw== -acorn-dynamic-import@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/acorn-dynamic-import/-/acorn-dynamic-import-3.0.0.tgz#901ceee4c7faaef7e07ad2a47e890675da50a278" - dependencies: - acorn "^5.0.0" +acorn@^6.0.5: + version "6.1.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.1.1.tgz#7d25ae05bb8ad1f9b699108e1094ecd7884adc1f" + integrity sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA== -acorn@^5.0.0, acorn@^5.6.2: - version "5.7.2" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.2.tgz#91fa871883485d06708800318404e72bfb26dcc5" - -aes-js@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/aes-js/-/aes-js-3.0.0.tgz#e21df10ad6c2053295bcbb8dab40b09dbea87e4d" +ajv-errors@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/ajv-errors/-/ajv-errors-1.0.1.tgz#f35986aceb91afadec4102fbd85014950cefa64d" + integrity sha512-DCRfO/4nQ+89p/RK43i8Ezd41EqdGIU4ld7nGF8OQ14oc/we5rEntLCUa7+jrn3nn83BosfwZA0wb4pon2o8iQ== ajv-keywords@^3.1.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.2.0.tgz#e86b819c602cf8821ad637413698f1dec021847a" + version "3.4.0" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.4.0.tgz#4b831e7b531415a7cc518cd404e73f6193c6349d" + integrity sha512-aUjdRFISbuFOl0EIZc+9e4FfZp0bDZgAdOOf30bJmw8VM9v84SHyVyxDfbWxpGYbdZD/9XoKxfHVNmxPkhwyGw== -ajv@^5.1.0, ajv@^5.3.0: +ajv@^5.1.0: version "5.5.2" resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.5.2.tgz#73b5eeca3fab653e3d3f9422b341ad42205dc965" + integrity sha1-c7Xuyj+rZT49P5Qis0GtQiBdyWU= dependencies: co "^4.6.0" fast-deep-equal "^1.0.0" @@ -285,8 +893,9 @@ ajv@^5.1.0, ajv@^5.3.0: json-schema-traverse "^0.3.0" ajv@^6.1.0: - version "6.5.3" - resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.5.3.tgz#71a569d189ecf4f4f321224fecb166f071dd90f9" + version "6.10.0" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.10.0.tgz#90d0d54439da587cd7e843bfb7045f50bd22bdf1" + integrity sha512-nffhOpkymDECQyR0mnsUtoCE8RlX38G0rYP+wgLWFyZuUyuuojSSvi/+euOiQBIn63whYwYVIIH1TvE3tu4OEg== dependencies: fast-deep-equal "^2.0.1" fast-json-stable-stringify "^2.0.0" @@ -296,32 +905,34 @@ ajv@^6.1.0: ansi-escapes@^1.0.0: version "1.4.0" resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e" + integrity sha1-06ioOzGapneTZisT52HHkRQiMG4= ansi-regex@^2.0.0: version "2.1.1" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8= ansi-regex@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998" + integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg= ansi-styles@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" + integrity sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4= ansi-styles@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d" + integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA== dependencies: color-convert "^1.9.0" -any-promise@1.3.0, any-promise@^1.0.0, any-promise@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" - anymatch@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" + integrity sha512-5teOsQWABXHHBFP9y3skS5P3d/WfWXpv3FUpy+LorMrNYaT9pI4oLMQX7jzQ2KklNpGpWHzdCXTDT2Y3XGlZBw== dependencies: micromatch "^3.1.4" normalize-path "^2.1.1" @@ -329,10 +940,12 @@ anymatch@^2.0.0: aproba@^1.0.3, aproba@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a" + integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw== are-we-there-yet@~1.1.2: version "1.1.5" resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz#4b35c2944f062a8bfcda66410760350fe9ddfc21" + integrity sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w== dependencies: delegates "^1.0.0" readable-stream "^2.0.6" @@ -340,26 +953,27 @@ are-we-there-yet@~1.1.2: arr-diff@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" + integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= arr-flatten@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" + integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== arr-union@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" - -array-flatten@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= array-unique@^0.3.2: version "0.3.2" resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" + integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= asn1.js@^4.0.0: version "4.10.1" resolved "https://registry.yarnpkg.com/asn1.js/-/asn1.js-4.10.1.tgz#b9c2bf5805f1e64aadeed6df3a2bfafb5a73f5a0" + integrity sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw== dependencies: bn.js "^4.0.0" inherits "^2.0.1" @@ -368,626 +982,91 @@ asn1.js@^4.0.0: asn1@~0.2.3: version "0.2.4" resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.4.tgz#8d2475dfab553bb33e77b54e59e880bb8ce23136" + integrity sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg== dependencies: safer-buffer "~2.1.0" assert-plus@1.0.0, assert-plus@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + integrity sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU= assert@^1.1.1: version "1.4.1" resolved "https://registry.yarnpkg.com/assert/-/assert-1.4.1.tgz#99912d591836b5a6f5b345c0f07eefc08fc65d91" + integrity sha1-mZEtWRg2tab1s0XA8H7vwI/GXZE= dependencies: util "0.10.3" assign-symbols@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" + integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= -async-each@^1.0.0: +async-each@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d" - -async-limiter@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.0.tgz#78faed8c3d074ab81f22b4e985d79e8738f720f8" + integrity sha1-GdOGodntxufByF04iu28xW0zYC0= async@2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/async/-/async-2.4.0.tgz#4990200f18ea5b837c2cc4f8c031a6985c385611" + integrity sha1-SZAgDxjqW4N8LMT4wDGmmFw4VhE= dependencies: lodash "^4.14.0" asynckit@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + integrity sha1-x57Zf380y48robyXkLzDZkdLS3k= atob@^2.1.1: version "2.1.2" resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" + integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== aws-sign2@~0.7.0: version "0.7.0" resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8" + integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg= -aws4@^1.6.0, aws4@^1.8.0: +aws4@^1.6.0: version "1.8.0" resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f" + integrity sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ== -babel-code-frame@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.26.0.tgz#63fd43f7dc1e3bb7ce35947db8fe369a3f58c74b" +babel-loader@^8.0.2: + version "8.0.5" + resolved "https://registry.yarnpkg.com/babel-loader/-/babel-loader-8.0.5.tgz#225322d7509c2157655840bba52e46b6c2f2fe33" + integrity sha512-NTnHnVRd2JnRqPC0vW+iOQWU5pchDbYXsG2E6DMXEpMfUcQKclF9gmf3G3ZMhzG7IG9ji4coL0cm+FxeWxDpnw== dependencies: - chalk "^1.1.3" - esutils "^2.0.2" - js-tokens "^3.0.2" - -babel-core@6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.0.tgz#af32f78b31a6fcef119c87b0fd8d9753f03a0bb8" - dependencies: - babel-code-frame "^6.26.0" - babel-generator "^6.26.0" - babel-helpers "^6.24.1" - babel-messages "^6.23.0" - babel-register "^6.26.0" - babel-runtime "^6.26.0" - babel-template "^6.26.0" - babel-traverse "^6.26.0" - babel-types "^6.26.0" - babylon "^6.18.0" - convert-source-map "^1.5.0" - debug "^2.6.8" - json5 "^0.5.1" - lodash "^4.17.4" - minimatch "^3.0.4" - path-is-absolute "^1.0.1" - private "^0.1.7" - slash "^1.0.0" - source-map "^0.5.6" - -babel-core@^6.26.0: - version "6.26.3" - resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.26.3.tgz#b2e2f09e342d0f0c88e2f02e067794125e75c207" - dependencies: - babel-code-frame "^6.26.0" - babel-generator "^6.26.0" - babel-helpers "^6.24.1" - babel-messages "^6.23.0" - babel-register "^6.26.0" - babel-runtime "^6.26.0" - babel-template "^6.26.0" - babel-traverse "^6.26.0" - babel-types "^6.26.0" - babylon "^6.18.0" - convert-source-map "^1.5.1" - debug "^2.6.9" - json5 "^0.5.1" - lodash "^4.17.4" - minimatch "^3.0.4" - path-is-absolute "^1.0.1" - private "^0.1.8" - slash "^1.0.0" - source-map "^0.5.7" - -babel-generator@^6.26.0: - version "6.26.1" - resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.26.1.tgz#1844408d3b8f0d35a404ea7ac180f087a601bd90" - dependencies: - babel-messages "^6.23.0" - babel-runtime "^6.26.0" - babel-types "^6.26.0" - detect-indent "^4.0.0" - jsesc "^1.3.0" - lodash "^4.17.4" - source-map "^0.5.7" - trim-right "^1.0.1" - -babel-helper-builder-binary-assignment-operator-visitor@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz#cce4517ada356f4220bcae8a02c2b346f9a56664" - dependencies: - babel-helper-explode-assignable-expression "^6.24.1" - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-helper-builder-react-jsx@^6.24.1: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-helper-builder-react-jsx/-/babel-helper-builder-react-jsx-6.26.0.tgz#39ff8313b75c8b65dceff1f31d383e0ff2a408a0" - dependencies: - babel-runtime "^6.26.0" - babel-types "^6.26.0" - esutils "^2.0.2" - -babel-helper-call-delegate@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz#ece6aacddc76e41c3461f88bfc575bd0daa2df8d" - dependencies: - babel-helper-hoist-variables "^6.24.1" - babel-runtime "^6.22.0" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - -babel-helper-define-map@^6.24.1: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-helper-define-map/-/babel-helper-define-map-6.26.0.tgz#a5f56dab41a25f97ecb498c7ebaca9819f95be5f" - dependencies: - babel-helper-function-name "^6.24.1" - babel-runtime "^6.26.0" - babel-types "^6.26.0" - lodash "^4.17.4" - -babel-helper-explode-assignable-expression@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz#f25b82cf7dc10433c55f70592d5746400ac22caa" - dependencies: - babel-runtime "^6.22.0" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - -babel-helper-function-name@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz#d3475b8c03ed98242a25b48351ab18399d3580a9" - dependencies: - babel-helper-get-function-arity "^6.24.1" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - -babel-helper-get-function-arity@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz#8f7782aa93407c41d3aa50908f89b031b1b6853d" - dependencies: - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-helper-hoist-variables@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz#1ecb27689c9d25513eadbc9914a73f5408be7a76" - dependencies: - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-helper-optimise-call-expression@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz#f7a13427ba9f73f8f4fa993c54a97882d1244257" - dependencies: - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-helper-regex@^6.24.1: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-helper-regex/-/babel-helper-regex-6.26.0.tgz#325c59f902f82f24b74faceed0363954f6495e72" - dependencies: - babel-runtime "^6.26.0" - babel-types "^6.26.0" - lodash "^4.17.4" - -babel-helper-remap-async-to-generator@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz#5ec581827ad723fecdd381f1c928390676e4551b" - dependencies: - babel-helper-function-name "^6.24.1" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - -babel-helper-replace-supers@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz#bf6dbfe43938d17369a213ca8a8bf74b6a90ab1a" - dependencies: - babel-helper-optimise-call-expression "^6.24.1" - babel-messages "^6.23.0" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - -babel-helpers@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-helpers/-/babel-helpers-6.24.1.tgz#3471de9caec388e5c850e597e58a26ddf37602b2" - dependencies: - babel-runtime "^6.22.0" - babel-template "^6.24.1" - -babel-loader@7.1.4: - version "7.1.4" - resolved "http://registry.npmjs.org/babel-loader/-/babel-loader-7.1.4.tgz#e3463938bd4e6d55d1c174c5485d406a188ed015" - dependencies: - find-cache-dir "^1.0.0" + find-cache-dir "^2.0.0" loader-utils "^1.0.2" mkdirp "^0.5.1" + util.promisify "^1.0.0" -babel-messages@^6.23.0: - version "6.23.0" - resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e" - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-check-es2015-constants@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz#35157b101426fd2ffd3da3f75c7d1e91835bbf8a" - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-syntax-async-functions@^6.8.0: - version "6.13.0" - resolved "http://registry.npmjs.org/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz#cad9cad1191b5ad634bf30ae0872391e0647be95" - -babel-plugin-syntax-exponentiation-operator@^6.8.0: - version "6.13.0" - resolved "http://registry.npmjs.org/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz#9ee7e8337290da95288201a6a57f4170317830de" - -babel-plugin-syntax-flow@^6.18.0: - version "6.18.0" - resolved "http://registry.npmjs.org/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz#4c3ab20a2af26aa20cd25995c398c4eb70310c8d" - -babel-plugin-syntax-jsx@^6.3.13, babel-plugin-syntax-jsx@^6.8.0: - version "6.18.0" - resolved "http://registry.npmjs.org/babel-plugin-syntax-jsx/-/babel-plugin-syntax-jsx-6.18.0.tgz#0af32a9a6e13ca7a3fd5069e62d7b0f58d0d8946" - -babel-plugin-syntax-trailing-function-commas@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz#ba0360937f8d06e40180a43fe0d5616fff532cf3" - -babel-plugin-transform-async-to-generator@^6.22.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz#6536e378aff6cb1d5517ac0e40eb3e9fc8d08761" - dependencies: - babel-helper-remap-async-to-generator "^6.24.1" - babel-plugin-syntax-async-functions "^6.8.0" - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-arrow-functions@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz#452692cb711d5f79dc7f85e440ce41b9f244d221" - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-block-scoped-functions@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz#bbc51b49f964d70cb8d8e0b94e820246ce3a6141" - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-block-scoping@^6.23.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.26.0.tgz#d70f5299c1308d05c12f463813b0a09e73b1895f" - dependencies: - babel-runtime "^6.26.0" - babel-template "^6.26.0" - babel-traverse "^6.26.0" - babel-types "^6.26.0" - lodash "^4.17.4" - -babel-plugin-transform-es2015-classes@^6.23.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz#5a4c58a50c9c9461e564b4b2a3bfabc97a2584db" - dependencies: - babel-helper-define-map "^6.24.1" - babel-helper-function-name "^6.24.1" - babel-helper-optimise-call-expression "^6.24.1" - babel-helper-replace-supers "^6.24.1" - babel-messages "^6.23.0" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - -babel-plugin-transform-es2015-computed-properties@^6.22.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz#6fe2a8d16895d5634f4cd999b6d3480a308159b3" - dependencies: - babel-runtime "^6.22.0" - babel-template "^6.24.1" - -babel-plugin-transform-es2015-destructuring@^6.23.0: - version "6.23.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz#997bb1f1ab967f682d2b0876fe358d60e765c56d" - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-duplicate-keys@^6.22.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz#73eb3d310ca969e3ef9ec91c53741a6f1576423e" - dependencies: - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-plugin-transform-es2015-for-of@^6.23.0: - version "6.23.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz#f47c95b2b613df1d3ecc2fdb7573623c75248691" - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-function-name@^6.22.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz#834c89853bc36b1af0f3a4c5dbaa94fd8eacaa8b" - dependencies: - babel-helper-function-name "^6.24.1" - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-plugin-transform-es2015-literals@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz#4f54a02d6cd66cf915280019a31d31925377ca2e" - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-modules-amd@^6.22.0, babel-plugin-transform-es2015-modules-amd@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz#3b3e54017239842d6d19c3011c4bd2f00a00d154" - dependencies: - babel-plugin-transform-es2015-modules-commonjs "^6.24.1" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - -babel-plugin-transform-es2015-modules-commonjs@^6.23.0, babel-plugin-transform-es2015-modules-commonjs@^6.24.1: - version "6.26.2" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.26.2.tgz#58a793863a9e7ca870bdc5a881117ffac27db6f3" - dependencies: - babel-plugin-transform-strict-mode "^6.24.1" - babel-runtime "^6.26.0" - babel-template "^6.26.0" - babel-types "^6.26.0" - -babel-plugin-transform-es2015-modules-systemjs@^6.23.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz#ff89a142b9119a906195f5f106ecf305d9407d23" - dependencies: - babel-helper-hoist-variables "^6.24.1" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - -babel-plugin-transform-es2015-modules-umd@^6.23.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz#ac997e6285cd18ed6176adb607d602344ad38468" - dependencies: - babel-plugin-transform-es2015-modules-amd "^6.24.1" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - -babel-plugin-transform-es2015-object-super@^6.22.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz#24cef69ae21cb83a7f8603dad021f572eb278f8d" - dependencies: - babel-helper-replace-supers "^6.24.1" - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-parameters@^6.23.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz#57ac351ab49caf14a97cd13b09f66fdf0a625f2b" - dependencies: - babel-helper-call-delegate "^6.24.1" - babel-helper-get-function-arity "^6.24.1" - babel-runtime "^6.22.0" - babel-template "^6.24.1" - babel-traverse "^6.24.1" - babel-types "^6.24.1" - -babel-plugin-transform-es2015-shorthand-properties@^6.22.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz#24f875d6721c87661bbd99a4622e51f14de38aa0" - dependencies: - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-plugin-transform-es2015-spread@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz#d6d68a99f89aedc4536c81a542e8dd9f1746f8d1" - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-sticky-regex@^6.22.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz#00c1cdb1aca71112cdf0cf6126c2ed6b457ccdbc" - dependencies: - babel-helper-regex "^6.24.1" - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-plugin-transform-es2015-template-literals@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz#a84b3450f7e9f8f1f6839d6d687da84bb1236d8d" - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-typeof-symbol@^6.23.0: - version "6.23.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz#dec09f1cddff94b52ac73d505c84df59dcceb372" - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-es2015-unicode-regex@^6.22.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz#d38b12f42ea7323f729387f18a7c5ae1faeb35e9" - dependencies: - babel-helper-regex "^6.24.1" - babel-runtime "^6.22.0" - regexpu-core "^2.0.0" - -babel-plugin-transform-exponentiation-operator@^6.22.0: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz#2ab0c9c7f3098fa48907772bb813fe41e8de3a0e" - dependencies: - babel-helper-builder-binary-assignment-operator-visitor "^6.24.1" - babel-plugin-syntax-exponentiation-operator "^6.8.0" - babel-runtime "^6.22.0" - -babel-plugin-transform-flow-strip-types@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-flow-strip-types/-/babel-plugin-transform-flow-strip-types-6.22.0.tgz#84cb672935d43714fdc32bce84568d87441cf7cf" - dependencies: - babel-plugin-syntax-flow "^6.18.0" - babel-runtime "^6.22.0" - -babel-plugin-transform-react-display-name@^6.23.0: - version "6.25.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-display-name/-/babel-plugin-transform-react-display-name-6.25.0.tgz#67e2bf1f1e9c93ab08db96792e05392bf2cc28d1" - dependencies: - babel-runtime "^6.22.0" - -babel-plugin-transform-react-jsx-self@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-jsx-self/-/babel-plugin-transform-react-jsx-self-6.22.0.tgz#df6d80a9da2612a121e6ddd7558bcbecf06e636e" - dependencies: - babel-plugin-syntax-jsx "^6.8.0" - babel-runtime "^6.22.0" - -babel-plugin-transform-react-jsx-source@^6.22.0: - version "6.22.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-jsx-source/-/babel-plugin-transform-react-jsx-source-6.22.0.tgz#66ac12153f5cd2d17b3c19268f4bf0197f44ecd6" - dependencies: - babel-plugin-syntax-jsx "^6.8.0" - babel-runtime "^6.22.0" - -babel-plugin-transform-react-jsx@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-react-jsx/-/babel-plugin-transform-react-jsx-6.24.1.tgz#840a028e7df460dfc3a2d29f0c0d91f6376e66a3" - dependencies: - babel-helper-builder-react-jsx "^6.24.1" - babel-plugin-syntax-jsx "^6.8.0" - babel-runtime "^6.22.0" - -babel-plugin-transform-regenerator@^6.22.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.26.0.tgz#e0703696fbde27f0a3efcacf8b4dca2f7b3a8f2f" - dependencies: - regenerator-transform "^0.10.0" - -babel-plugin-transform-strict-mode@^6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz#d5faf7aa578a65bbe591cf5edae04a0c67020758" - dependencies: - babel-runtime "^6.22.0" - babel-types "^6.24.1" - -babel-preset-env@1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/babel-preset-env/-/babel-preset-env-1.6.0.tgz#2de1c782a780a0a5d605d199c957596da43c44e4" - dependencies: - babel-plugin-check-es2015-constants "^6.22.0" - babel-plugin-syntax-trailing-function-commas "^6.22.0" - babel-plugin-transform-async-to-generator "^6.22.0" - babel-plugin-transform-es2015-arrow-functions "^6.22.0" - babel-plugin-transform-es2015-block-scoped-functions "^6.22.0" - babel-plugin-transform-es2015-block-scoping "^6.23.0" - babel-plugin-transform-es2015-classes "^6.23.0" - babel-plugin-transform-es2015-computed-properties "^6.22.0" - babel-plugin-transform-es2015-destructuring "^6.23.0" - babel-plugin-transform-es2015-duplicate-keys "^6.22.0" - babel-plugin-transform-es2015-for-of "^6.23.0" - babel-plugin-transform-es2015-function-name "^6.22.0" - babel-plugin-transform-es2015-literals "^6.22.0" - babel-plugin-transform-es2015-modules-amd "^6.22.0" - babel-plugin-transform-es2015-modules-commonjs "^6.23.0" - babel-plugin-transform-es2015-modules-systemjs "^6.23.0" - babel-plugin-transform-es2015-modules-umd "^6.23.0" - babel-plugin-transform-es2015-object-super "^6.22.0" - babel-plugin-transform-es2015-parameters "^6.23.0" - babel-plugin-transform-es2015-shorthand-properties "^6.22.0" - babel-plugin-transform-es2015-spread "^6.22.0" - babel-plugin-transform-es2015-sticky-regex "^6.22.0" - babel-plugin-transform-es2015-template-literals "^6.22.0" - babel-plugin-transform-es2015-typeof-symbol "^6.23.0" - babel-plugin-transform-es2015-unicode-regex "^6.22.0" - babel-plugin-transform-exponentiation-operator "^6.22.0" - babel-plugin-transform-regenerator "^6.22.0" - browserslist "^2.1.2" - invariant "^2.2.2" - semver "^5.3.0" - -babel-preset-flow@^6.23.0: - version "6.23.0" - resolved "https://registry.yarnpkg.com/babel-preset-flow/-/babel-preset-flow-6.23.0.tgz#e71218887085ae9a24b5be4169affb599816c49d" - dependencies: - babel-plugin-transform-flow-strip-types "^6.22.0" - -babel-preset-react@6.24.1: - version "6.24.1" - resolved "https://registry.yarnpkg.com/babel-preset-react/-/babel-preset-react-6.24.1.tgz#ba69dfaea45fc3ec639b6a4ecea6e17702c91380" - dependencies: - babel-plugin-syntax-jsx "^6.3.13" - babel-plugin-transform-react-display-name "^6.23.0" - babel-plugin-transform-react-jsx "^6.24.1" - babel-plugin-transform-react-jsx-self "^6.22.0" - babel-plugin-transform-react-jsx-source "^6.22.0" - babel-preset-flow "^6.23.0" - -babel-register@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.26.0.tgz#6ed021173e2fcb486d7acb45c6009a856f647071" - dependencies: - babel-core "^6.26.0" - babel-runtime "^6.26.0" - core-js "^2.5.0" - home-or-tmp "^2.0.0" - lodash "^4.17.4" - mkdirp "^0.5.1" - source-map-support "^0.4.15" - -babel-runtime@^6.18.0, babel-runtime@^6.22.0, babel-runtime@^6.26.0: +babel-runtime@^6.18.0: version "6.26.0" resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.26.0.tgz#965c7058668e82b55d7bfe04ff2337bc8b5647fe" + integrity sha1-llxwWGaOgrVde/4E/yM3vItWR/4= dependencies: core-js "^2.4.0" regenerator-runtime "^0.11.0" -babel-template@^6.24.1, babel-template@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.26.0.tgz#de03e2d16396b069f46dd9fff8521fb1a0e35e02" - dependencies: - babel-runtime "^6.26.0" - babel-traverse "^6.26.0" - babel-types "^6.26.0" - babylon "^6.18.0" - lodash "^4.17.4" - -babel-traverse@^6.24.1, babel-traverse@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.26.0.tgz#46a9cbd7edcc62c8e5c064e2d2d8d0f4035766ee" - dependencies: - babel-code-frame "^6.26.0" - babel-messages "^6.23.0" - babel-runtime "^6.26.0" - babel-types "^6.26.0" - babylon "^6.18.0" - debug "^2.6.8" - globals "^9.18.0" - invariant "^2.2.2" - lodash "^4.17.4" - -babel-types@^6.19.0, babel-types@^6.24.1, babel-types@^6.26.0: - version "6.26.0" - resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.26.0.tgz#a3b073f94ab49eb6fa55cd65227a334380632497" - dependencies: - babel-runtime "^6.26.0" - esutils "^2.0.2" - lodash "^4.17.4" - to-fast-properties "^1.0.3" - -babylon@^6.18.0: - version "6.18.0" - resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.18.0.tgz#af2f3b88fa6f5c1e4c634d1a0f8eac4f55b395e3" - balanced-match@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" - -base64-js@0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-0.0.8.tgz#1101e9544f4a76b1bc3b26d452ca96d7a35e7978" + integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= base64-js@^1.0.2: version "1.3.0" resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.0.tgz#cab1e6118f051095e58b5281aea8c1cd22bfc0e3" + integrity sha512-ccav/yGvoa80BQDljCxsmmQ3Xvx60/UpBIij5QN21W3wBi/hhIC9OoO+KLpu9IJTS9j4DRVJ3aDDF9cMSoa2lw== base@^0.11.1: version "0.11.2" resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" + integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== dependencies: cache-base "^1.0.1" class-utils "^0.3.5" @@ -1000,100 +1079,47 @@ base@^0.11.1: bcrypt-pbkdf@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz#a4301d389b6a43f9b67ff3ca11a3f6637e360e9e" + integrity sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4= dependencies: tweetnacl "^0.14.3" -big.js@^3.1.3: - version "3.2.0" - resolved "https://registry.yarnpkg.com/big.js/-/big.js-3.2.0.tgz#a5fc298b81b9e0dca2e458824784b65c52ba588e" +big.js@^5.2.2: + version "5.2.2" + resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" + integrity sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ== binary-extensions@^1.0.0: - version "1.11.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.11.0.tgz#46aa1751fb6a2f93ee5e689bb1087d4b14c6c205" - -bindings@^1.2.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.3.0.tgz#b346f6ecf6a95f5a815c5839fc7cdb22502f1ed7" - -bip66@^1.1.3: - version "1.1.5" - resolved "https://registry.yarnpkg.com/bip66/-/bip66-1.1.5.tgz#01fa8748785ca70955d5011217d1b3139969ca22" - dependencies: - safe-buffer "^5.0.1" - -bl@^1.0.0: - version "1.2.2" - resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.2.tgz#a160911717103c07410cef63ef51b397c025af9c" - dependencies: - readable-stream "^2.3.5" - safe-buffer "^5.1.1" - -block-stream@*: - version "0.0.9" - resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a" - dependencies: - inherits "~2.0.0" + version "1.13.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.0.tgz#9523e001306a32444b907423f1de2164222f6ab1" + integrity sha512-EgmjVLMn22z7eGGv3kcnHwSnJXmFHjISTY9E/S5lIcTD3Oxw05QTcBLNkJFzcb3cNueUdF/IN4U+d78V0zO8Hw== bluebird@3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.0.tgz#791420d7f551eea2897453a8a77653f96606d67c" + integrity sha1-eRQg1/VR7qKJdFOop3ZT+WYG1nw= -bluebird@^2.9.34: - version "2.11.0" - resolved "http://registry.npmjs.org/bluebird/-/bluebird-2.11.0.tgz#534b9033c022c9579c56ba3b3e5a5caafbb650e1" +bluebird@^3.5.3: + version "3.5.3" + resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.3.tgz#7d01c6f9616c9a51ab0f8c549a79dfe6ec33efa7" + integrity sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw== -bluebird@^3.5.0, bluebird@^3.5.1: - version "3.5.2" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.2.tgz#1be0908e054a751754549c270489c1505d4ab15a" - -bn.js@4.11.6: - version "4.11.6" - resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.6.tgz#53344adb14617a13f6e8dd2ce28905d1c0ba3215" - -bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.10.0, bn.js@^4.11.0, bn.js@^4.11.3, bn.js@^4.11.6, bn.js@^4.4.0, bn.js@^4.8.0: +bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0: version "4.11.8" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f" - -body-parser@1.18.2: - version "1.18.2" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.2.tgz#87678a19d84b47d859b83199bd59bce222b10454" - dependencies: - bytes "3.0.0" - content-type "~1.0.4" - debug "2.6.9" - depd "~1.1.1" - http-errors "~1.6.2" - iconv-lite "0.4.19" - on-finished "~2.3.0" - qs "6.5.1" - raw-body "2.3.2" - type-is "~1.6.15" - -body-parser@^1.16.0: - version "1.18.3" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.18.3.tgz#5b292198ffdd553b3a0f20ded0592b956955c8b4" - dependencies: - bytes "3.0.0" - content-type "~1.0.4" - debug "2.6.9" - depd "~1.1.2" - http-errors "~1.6.3" - iconv-lite "0.4.23" - on-finished "~2.3.0" - qs "6.5.2" - raw-body "2.3.3" - type-is "~1.6.16" + integrity sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA== brace-expansion@^1.1.7: version "1.1.11" resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd" + integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA== dependencies: balanced-match "^1.0.0" concat-map "0.0.1" -braces@^2.3.0, braces@^2.3.1: +braces@^2.3.1, braces@^2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" + integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== dependencies: arr-flatten "^1.1.0" array-unique "^0.3.2" @@ -1109,10 +1135,12 @@ braces@^2.3.0, braces@^2.3.1: brorand@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/brorand/-/brorand-1.1.0.tgz#12c25efe40a45e3c323eb8675a0a0ce57b22371f" + integrity sha1-EsJe/kCkXjwyPrhnWgoM5XsiNx8= -browserify-aes@^1.0.0, browserify-aes@^1.0.4, browserify-aes@^1.0.6: +browserify-aes@^1.0.0, browserify-aes@^1.0.4: version "1.2.0" resolved "https://registry.yarnpkg.com/browserify-aes/-/browserify-aes-1.2.0.tgz#326734642f403dabc3003209853bb70ad428ef48" + integrity sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA== dependencies: buffer-xor "^1.0.3" cipher-base "^1.0.0" @@ -1124,6 +1152,7 @@ browserify-aes@^1.0.0, browserify-aes@^1.0.4, browserify-aes@^1.0.6: browserify-cipher@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/browserify-cipher/-/browserify-cipher-1.0.1.tgz#8d6474c1b870bfdabcd3bcfcc1934a10e94f15f0" + integrity sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w== dependencies: browserify-aes "^1.0.4" browserify-des "^1.0.0" @@ -1132,6 +1161,7 @@ browserify-cipher@^1.0.0: browserify-des@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/browserify-des/-/browserify-des-1.0.2.tgz#3af4f1f59839403572f1c66204375f7a7f703e9c" + integrity sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A== dependencies: cipher-base "^1.0.1" des.js "^1.0.0" @@ -1141,19 +1171,15 @@ browserify-des@^1.0.0: browserify-rsa@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/browserify-rsa/-/browserify-rsa-4.0.1.tgz#21e0abfaf6f2029cf2fafb133567a701d4135524" + integrity sha1-IeCr+vbyApzy+vsTNWenAdQTVSQ= dependencies: bn.js "^4.1.0" randombytes "^2.0.1" -browserify-sha3@^0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/browserify-sha3/-/browserify-sha3-0.0.1.tgz#3ff34a3006ef15c0fb3567e541b91a2340123d11" - dependencies: - js-sha3 "^0.3.1" - browserify-sign@^4.0.0: version "4.0.4" resolved "https://registry.yarnpkg.com/browserify-sign/-/browserify-sign-4.0.4.tgz#aa4eb68e5d7b658baa6bf6a57e630cbd7a93d298" + integrity sha1-qk62jl17ZYuqa/alfmMMvXqT0pg= dependencies: bn.js "^4.1.1" browserify-rsa "^4.0.0" @@ -1166,99 +1192,72 @@ browserify-sign@^4.0.0: browserify-zlib@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/browserify-zlib/-/browserify-zlib-0.2.0.tgz#2869459d9aa3be245fe8fe2ca1f46e2e7f54d73f" + integrity sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA== dependencies: pako "~1.0.5" -browserslist@^2.1.2: - version "2.11.3" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-2.11.3.tgz#fe36167aed1bbcde4827ebfe71347a2cc70b99b2" +browserslist@^4.3.4: + version "4.4.2" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.4.2.tgz#6ea8a74d6464bb0bd549105f659b41197d8f0ba2" + integrity sha512-ISS/AIAiHERJ3d45Fz0AVYKkgcy+F/eJHzKEvv1j0wwKGKD9T3BrwKr/5g45L+Y4XIK5PlTqefHciRFcfE1Jxg== dependencies: - caniuse-lite "^1.0.30000792" - electron-to-chromium "^1.3.30" - -buffer-alloc-unsafe@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz#bd7dc26ae2972d0eda253be061dba992349c19f0" - -buffer-alloc@^1.1.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/buffer-alloc/-/buffer-alloc-1.2.0.tgz#890dd90d923a873e08e10e5fd51a57e5b7cce0ec" - dependencies: - buffer-alloc-unsafe "^1.1.0" - buffer-fill "^1.0.0" + caniuse-lite "^1.0.30000939" + electron-to-chromium "^1.3.113" + node-releases "^1.1.8" buffer-crc32@~0.2.3: version "0.2.13" resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" - -buffer-fill@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/buffer-fill/-/buffer-fill-1.0.0.tgz#f8f78b76789888ef39f205cd637f68e702122b2c" + integrity sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI= buffer-from@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef" - -buffer-to-arraybuffer@^0.0.5: - version "0.0.5" - resolved "https://registry.yarnpkg.com/buffer-to-arraybuffer/-/buffer-to-arraybuffer-0.0.5.tgz#6064a40fa76eb43c723aba9ef8f6e1216d10511a" + integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A== buffer-xor@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/buffer-xor/-/buffer-xor-1.0.3.tgz#26e61ed1422fb70dd42e6e36729ed51d855fe8d9" - -buffer@^3.0.1: - version "3.6.0" - resolved "http://registry.npmjs.org/buffer/-/buffer-3.6.0.tgz#a72c936f77b96bf52f5f7e7b467180628551defb" - dependencies: - base64-js "0.0.8" - ieee754 "^1.1.4" - isarray "^1.0.0" + integrity sha1-JuYe0UIvtw3ULm42cp7VHYVf6Nk= buffer@^4.3.0: version "4.9.1" - resolved "http://registry.npmjs.org/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298" + resolved "https://registry.yarnpkg.com/buffer/-/buffer-4.9.1.tgz#6d1bb601b07a4efced97094132093027c95bc298" + integrity sha1-bRu2AbB6TvztlwlBMgkwJ8lbwpg= dependencies: base64-js "^1.0.2" ieee754 "^1.1.4" isarray "^1.0.0" -buffer@^5.0.5, buffer@^5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/buffer/-/buffer-5.2.1.tgz#dd57fa0f109ac59c602479044dca7b8b3d0b71d6" - dependencies: - base64-js "^1.0.2" - ieee754 "^1.1.4" - builtin-status-codes@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8" + integrity sha1-hZgoeOIbmOHGZCXgPQF0eI9Wnug= -bytes@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" - -cacache@^10.0.4: - version "10.0.4" - resolved "https://registry.yarnpkg.com/cacache/-/cacache-10.0.4.tgz#6452367999eff9d4188aefd9a14e9d7c6a263460" +cacache@^11.0.2: + version "11.3.2" + resolved "https://registry.yarnpkg.com/cacache/-/cacache-11.3.2.tgz#2d81e308e3d258ca38125b676b98b2ac9ce69bfa" + integrity sha512-E0zP4EPGDOaT2chM08Als91eYnf8Z+eH1awwwVsngUmgppfM5jjJ8l3z5vO5p5w/I3LsiXawb1sW0VY65pQABg== dependencies: - bluebird "^3.5.1" - chownr "^1.0.1" - glob "^7.1.2" - graceful-fs "^4.1.11" - lru-cache "^4.1.1" - mississippi "^2.0.0" + bluebird "^3.5.3" + chownr "^1.1.1" + figgy-pudding "^3.5.1" + glob "^7.1.3" + graceful-fs "^4.1.15" + lru-cache "^5.1.1" + mississippi "^3.0.0" mkdirp "^0.5.1" move-concurrently "^1.0.1" promise-inflight "^1.0.1" rimraf "^2.6.2" - ssri "^5.2.4" - unique-filename "^1.1.0" + ssri "^6.0.1" + unique-filename "^1.1.1" y18n "^4.0.0" cache-base@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" + integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== dependencies: collection-visit "^1.0.0" component-emitter "^1.2.1" @@ -1273,20 +1272,24 @@ cache-base@^1.0.1: cachedir@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/cachedir/-/cachedir-1.3.0.tgz#5e01928bf2d95b5edd94b0942188246740e0dbc4" + integrity sha512-O1ji32oyON9laVPJL1IZ5bmwd2cB46VfpxkDequezH+15FDzzVddEyrGEeX4WusDSqKxdyFdDQDEG1yo1GoWkg== dependencies: os-homedir "^1.0.1" -caniuse-lite@^1.0.30000792: - version "1.0.30000885" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000885.tgz#e889e9f8e7e50e769f2a49634c932b8aee622984" +caniuse-lite@^1.0.30000939: + version "1.0.30000942" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30000942.tgz#454139b28274bce70bfe1d50c30970df7430c6e4" + integrity sha512-wLf+IhZUy2rfz48tc40OH7jHjXjnvDFEYqBHluINs/6MgzoNLPf25zhE4NOVzqxLKndf+hau81sAW0RcGHIaBQ== caseless@~0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw= -chalk@2.4.1, chalk@^2.0.1, chalk@^2.3.0: +chalk@2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e" + integrity sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ== dependencies: ansi-styles "^3.2.1" escape-string-regexp "^1.0.5" @@ -1294,7 +1297,8 @@ chalk@2.4.1, chalk@^2.0.1, chalk@^2.3.0: chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3: version "1.1.3" - resolved "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" + integrity sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg= dependencies: ansi-styles "^2.2.1" escape-string-regexp "^1.0.2" @@ -1302,46 +1306,60 @@ chalk@^1.0.0, chalk@^1.1.1, chalk@^1.1.3: strip-ansi "^3.0.0" supports-color "^2.0.0" +chalk@^2.0.0, chalk@^2.0.1, chalk@^2.3.0: + version "2.4.2" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" + integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== + dependencies: + ansi-styles "^3.2.1" + escape-string-regexp "^1.0.5" + supports-color "^5.3.0" + check-more-types@2.24.0: version "2.24.0" resolved "https://registry.yarnpkg.com/check-more-types/-/check-more-types-2.24.0.tgz#1420ffb10fd444dcfc79b43891bbfffd32a84600" + integrity sha1-FCD/sQ/URNz8ebQ4kbv//TKoRgA= chokidar@^2.0.2: - version "2.0.4" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.0.4.tgz#356ff4e2b0e8e43e322d18a372460bbcf3accd26" + version "2.1.2" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-2.1.2.tgz#9c23ea40b01638439e0513864d362aeacc5ad058" + integrity sha512-IwXUx0FXc5ibYmPC2XeEj5mpXoV66sR+t3jqu2NS2GYwCktt3KF1/Qqjws/NkegajBA4RbZ5+DDwlOiJsxDHEg== dependencies: anymatch "^2.0.0" - async-each "^1.0.0" - braces "^2.3.0" + async-each "^1.0.1" + braces "^2.3.2" glob-parent "^3.1.0" - inherits "^2.0.1" + inherits "^2.0.3" is-binary-path "^1.0.0" is-glob "^4.0.0" - lodash.debounce "^4.0.8" - normalize-path "^2.1.1" + normalize-path "^3.0.0" path-is-absolute "^1.0.0" - readdirp "^2.0.0" - upath "^1.0.5" + readdirp "^2.2.1" + upath "^1.1.0" optionalDependencies: - fsevents "^1.2.2" + fsevents "^1.2.7" -chownr@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.0.1.tgz#e2a75042a9551908bebd25b8523d5f9769d79181" +chownr@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/chownr/-/chownr-1.1.1.tgz#54726b8b8fff4df053c42187e801fb4412df1494" + integrity sha512-j38EvO5+LHX84jlo6h4UzmOwi0UgW61WRyPtJz4qaadK5eY3BTS5TY/S1Stc3Uk2lIM6TPevAlULiEJwie860g== chrome-trace-event@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.0.tgz#45a91bd2c20c9411f0963b5aaeb9a1b95e09cc48" + integrity sha512-xDbVgyfDTT2piup/h8dK/y4QZfJRSa73bw1WZ8b4XM1o7fsFubUVGYcE+1ANtOzJJELGpYoG2961z0Z6OAld9A== dependencies: tslib "^1.9.0" ci-info@^1.0.0: - version "1.5.1" - resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.5.1.tgz#17e8eb5de6f8b2b6038f0cbb714d410bfa9f3030" + version "1.6.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.6.0.tgz#2ca20dbb9ceb32d4524a683303313f0304b1e497" + integrity sha512-vsGdkwSCDpWmP80ncATX7iea5DWQemg1UgCW5J8tqjU3lYw4FBYuj89J0CTVomA7BEfvSZd84GmHko+MxFQU2A== cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/cipher-base/-/cipher-base-1.0.4.tgz#8760e4ecc272f4c363532f926d874aae2c1397de" + integrity sha512-Kkht5ye6ZGmwv40uUDZztayT2ThLQGfnj/T71N/XzeZeo3nf8foyW7zGTsPYkEya3m5f3cAypH+qe7YOrM1U2Q== dependencies: inherits "^2.0.1" safe-buffer "^5.0.1" @@ -1349,6 +1367,7 @@ cipher-base@^1.0.0, cipher-base@^1.0.1, cipher-base@^1.0.3: class-utils@^0.3.5: version "0.3.6" resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" + integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== dependencies: arr-union "^3.1.0" define-property "^0.2.5" @@ -1358,16 +1377,19 @@ class-utils@^0.3.5: cli-cursor@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-1.0.2.tgz#64da3f7d56a54412e59794bd62dc35295e8f2987" + integrity sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc= dependencies: restore-cursor "^1.0.1" cli-spinners@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-0.1.2.tgz#bb764d88e185fb9e1e6a2a1f19772318f605e31c" + integrity sha1-u3ZNiOGF+54eaiofGXcjGPYF4xw= cli-truncate@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-0.2.1.tgz#9f15cfbb0705005369216c626ac7d05ab90dd574" + integrity sha1-nxXPuwcFAFNpIWxiasfQWrkN1XQ= dependencies: slice-ansi "0.0.4" string-width "^1.0.1" @@ -1375,14 +1397,17 @@ cli-truncate@^0.2.1: co@^4.6.0: version "4.6.0" resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + integrity sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ= code-point-at@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" + integrity sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c= collection-visit@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" + integrity sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA= dependencies: map-visit "^1.0.0" object-visit "^1.0.0" @@ -1390,58 +1415,58 @@ collection-visit@^1.0.0: color-convert@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" + integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg== dependencies: color-name "1.1.3" color-name@1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" + integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= -combined-stream@1.0.6, combined-stream@~1.0.5, combined-stream@~1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.6.tgz#723e7df6e801ac5613113a7e445a9b69cb632818" +combined-stream@^1.0.6, combined-stream@~1.0.5: + version "1.0.7" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.7.tgz#2d1d24317afb8abe95d6d2c0b07b57813539d828" + integrity sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w== dependencies: delayed-stream "~1.0.0" commander@2.11.0: version "2.11.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.11.0.tgz#157152fd1e7a6c8d98a5b715cf376df928004563" + integrity sha512-b0553uYA5YAEGgyYIGYROzKQ7X5RAqedkfjiZxwi0kL1g3bOaBNNZfYkzt/CL0umgD5wc9Jec2FbB98CjkMRvQ== -commander@^2.8.1: - version "2.18.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.18.0.tgz#2bf063ddee7c7891176981a2cc798e5754bc6970" - -commander@~2.13.0: - version "2.13.0" - resolved "https://registry.yarnpkg.com/commander/-/commander-2.13.0.tgz#6964bca67685df7c1f1430c584f07d7597885b9c" - -commander@~2.8.1: - version "2.8.1" - resolved "http://registry.npmjs.org/commander/-/commander-2.8.1.tgz#06be367febfda0c330aa1e2a072d3dc9762425d4" - dependencies: - graceful-readlink ">= 1.0.0" +commander@~2.17.1: + version "2.17.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.17.1.tgz#bd77ab7de6de94205ceacc72f1716d29f20a77bf" + integrity sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg== common-tags@1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/common-tags/-/common-tags-1.4.0.tgz#1187be4f3d4cf0c0427d43f74eef1f73501614c0" + integrity sha1-EYe+Tz1M8MBCfUP3Tu8fc1AWFMA= dependencies: babel-runtime "^6.18.0" commondir@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" + integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= component-emitter@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" + integrity sha1-E3kY1teCg/ffemt8WmPhQOaUJeY= concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= concat-stream@1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7" + integrity sha1-CqxmL9Ur54lk1VMvaUeE5wEQrPc= dependencies: inherits "^2.0.3" readable-stream "^2.2.2" @@ -1450,6 +1475,7 @@ concat-stream@1.6.0: concat-stream@^1.5.0: version "1.6.2" resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.2.tgz#904bdf194cd3122fc675c77fc4ac3d4ff0fd1a34" + integrity sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw== dependencies: buffer-from "^1.0.0" inherits "^2.0.3" @@ -1459,46 +1485,31 @@ concat-stream@^1.5.0: console-browserify@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/console-browserify/-/console-browserify-1.1.0.tgz#f0241c45730a9fc6323b206dbf38edc741d0bb10" + integrity sha1-8CQcRXMKn8YyOyBtvzjtx0HQuxA= dependencies: date-now "^0.1.4" console-control-strings@^1.0.0, console-control-strings@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= constants-browserify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75" + integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U= -content-disposition@0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4" - -content-type@~1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" - -convert-source-map@^1.5.0, convert-source-map@^1.5.1: +convert-source-map@^1.1.0: version "1.6.0" resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.6.0.tgz#51b537a8c43e0f04dec1993bffcdd504e758ac20" + integrity sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A== dependencies: safe-buffer "~5.1.1" -cookie-signature@1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" - -cookie@0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.3.1.tgz#e7e0a1f9ef43b4c8ba925c5c5a96e806d16873bb" - -cookiejar@^2.1.1: - version "2.1.2" - resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.2.tgz#dd8a235530752f988f9a0844f3fc589e3111125c" - copy-concurrently@^1.0.0: version "1.0.5" resolved "https://registry.yarnpkg.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0" + integrity sha512-f2domd9fsVDFtaFcbaRZuYXwtdmnzqbADSwhSWYxYB/Q8zsdUUFMXVRwXGDMWmbEzAn1kdRrtI1T/KTFOL4X2A== dependencies: aproba "^1.1.1" fs-write-stream-atomic "^1.0.8" @@ -1510,25 +1521,22 @@ copy-concurrently@^1.0.0: copy-descriptor@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" + integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= -core-js@^2.4.0, core-js@^2.5.0: - version "2.5.7" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.5.7.tgz#f972608ff0cead68b841a16a932d0b183791814e" +core-js@^2.4.0: + version "2.6.5" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.5.tgz#44bc8d249e7fb2ff5d00e0341a7ffb94fbf67895" + integrity sha512-klh/kDpwX8hryYL14M9w/xei6vrv6sE8gTHDG7/T/+SEovB/G4ejwcfE/CBzO6Edsu+OETZMZ3wcX/EjUkrl5A== core-util-is@1.0.2, core-util-is@~1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" - -cors@^2.8.1: - version "2.8.4" - resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.4.tgz#2bd381f2eb201020105cd50ea59da63090694686" - dependencies: - object-assign "^4" - vary "^1" + integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac= create-ecdh@^4.0.0: version "4.0.3" resolved "https://registry.yarnpkg.com/create-ecdh/-/create-ecdh-4.0.3.tgz#c9111b6f33045c4697f144787f9254cdc77c45ff" + integrity sha512-GbEHQPMOswGpKXM9kCWVrremUcBmjteUaQ01T9rkKCPDXfUHX0IoP9LpHYo2NPFampa4e+/pFDc3jQdxrxQLaw== dependencies: bn.js "^4.1.0" elliptic "^6.0.0" @@ -1536,6 +1544,7 @@ create-ecdh@^4.0.0: create-hash@^1.1.0, create-hash@^1.1.2: version "1.2.0" resolved "https://registry.yarnpkg.com/create-hash/-/create-hash-1.2.0.tgz#889078af11a63756bcfb59bd221996be3a9ef196" + integrity sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg== dependencies: cipher-base "^1.0.1" inherits "^2.0.1" @@ -1546,6 +1555,7 @@ create-hash@^1.1.0, create-hash@^1.1.2: create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: version "1.1.7" resolved "https://registry.yarnpkg.com/create-hmac/-/create-hmac-1.1.7.tgz#69170c78b3ab957147b2b8b04572e47ead2243ff" + integrity sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg== dependencies: cipher-base "^1.0.3" create-hash "^1.1.0" @@ -1557,6 +1567,7 @@ create-hmac@^1.1.0, create-hmac@^1.1.2, create-hmac@^1.1.4: cross-spawn@^6.0.0: version "6.0.5" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4" + integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ== dependencies: nice-try "^1.0.4" path-key "^2.0.1" @@ -1564,9 +1575,10 @@ cross-spawn@^6.0.0: shebang-command "^1.2.0" which "^1.2.9" -crypto-browserify@3.12.0, crypto-browserify@^3.11.0: +crypto-browserify@^3.11.0: version "3.12.0" resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec" + integrity sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg== dependencies: browserify-cipher "^1.0.0" browserify-sign "^4.0.0" @@ -1583,10 +1595,12 @@ crypto-browserify@3.12.0, crypto-browserify@^3.11.0: cyclist@~0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/cyclist/-/cyclist-0.2.2.tgz#1b33792e11e914a2fd6d6ed6447464444e5fa640" + integrity sha1-GzN5LhHpFKL9bW7WRHRkRE5fpkA= -cypress@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/cypress/-/cypress-3.1.0.tgz#b718ba64289b887c7ab7a7f09245d871a4a409ba" +cypress@^3.1.5: + version "3.1.5" + resolved "https://registry.yarnpkg.com/cypress/-/cypress-3.1.5.tgz#5227b2ce9306c47236d29e703bad9055d7218042" + integrity sha512-jzYGKJqU1CHoNocPndinf/vbG28SeU+hg+4qhousT/HDBMJxYgjecXOmSgBX/ga9/TakhqSrIrSP2r6gW/OLtg== dependencies: "@cypress/listr-verbose-renderer" "0.4.1" "@cypress/xvfb" "1.2.3" @@ -1594,12 +1608,12 @@ cypress@^3.1.0: "@types/bluebird" "3.5.18" "@types/chai" "4.0.8" "@types/chai-jquery" "1.1.35" - "@types/jquery" "3.2.16" + "@types/jquery" "3.3.6" "@types/lodash" "4.14.87" "@types/minimatch" "3.0.3" "@types/mocha" "2.2.44" - "@types/sinon" "4.0.0" - "@types/sinon-chai" "2.7.29" + "@types/sinon" "7.0.0" + "@types/sinon-chai" "3.2.2" bluebird "3.5.0" cachedir "1.3.0" chalk "2.4.1" @@ -1617,10 +1631,10 @@ cypress@^3.1.0: is-installed-globally "0.1.0" lazy-ass "1.6.0" listr "0.12.0" - lodash "4.17.10" + lodash "4.17.11" log-symbols "2.2.0" minimist "1.2.0" - progress "1.1.8" + moment "2.22.2" ramda "0.24.1" request "2.87.0" request-progress "0.3.1" @@ -1632,106 +1646,83 @@ cypress@^3.1.0: dashdash@^1.12.0: version "1.14.1" resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" + integrity sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA= dependencies: assert-plus "^1.0.0" date-fns@^1.27.2: - version "1.29.0" - resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.29.0.tgz#12e609cdcb935127311d04d33334e2960a2a54e6" + version "1.30.1" + resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-1.30.1.tgz#2e71bf0b119153dbb4cc4e88d9ea5acfb50dc05c" + integrity sha512-hBSVCvSmWC+QypYObzwGOd9wqdDpOt+0wl0KbU+R+uuZBS1jN8VsD1ss3irQDknRj5NvxiTF6oj/nDRnN/UQNw== date-now@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/date-now/-/date-now-0.1.4.tgz#eaf439fd4d4848ad74e5cc7dbef200672b9e345b" + integrity sha1-6vQ5/U1ISK105cx9vvIAZyueNFs= -debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9: +debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" + integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== dependencies: ms "2.0.0" -debug@3.1.0, debug@^3.1.0: +debug@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" + integrity sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g== dependencies: ms "2.0.0" +debug@^3.1.0: + version "3.2.6" + resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" + integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== + dependencies: + ms "^2.1.1" + +debug@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.1.1.tgz#3b72260255109c6b589cee050f1d516139664791" + integrity sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw== + dependencies: + ms "^2.1.1" + decode-uri-component@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" - -decompress-response@^3.2.0, decompress-response@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3" - dependencies: - mimic-response "^1.0.0" - -decompress-tar@^4.0.0, decompress-tar@^4.1.0, decompress-tar@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/decompress-tar/-/decompress-tar-4.1.1.tgz#718cbd3fcb16209716e70a26b84e7ba4592e5af1" - dependencies: - file-type "^5.2.0" - is-stream "^1.1.0" - tar-stream "^1.5.2" - -decompress-tarbz2@^4.0.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/decompress-tarbz2/-/decompress-tarbz2-4.1.1.tgz#3082a5b880ea4043816349f378b56c516be1a39b" - dependencies: - decompress-tar "^4.1.0" - file-type "^6.1.0" - is-stream "^1.1.0" - seek-bzip "^1.0.5" - unbzip2-stream "^1.0.9" - -decompress-targz@^4.0.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/decompress-targz/-/decompress-targz-4.1.1.tgz#c09bc35c4d11f3de09f2d2da53e9de23e7ce1eee" - dependencies: - decompress-tar "^4.1.1" - file-type "^5.2.0" - is-stream "^1.1.0" - -decompress-unzip@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/decompress-unzip/-/decompress-unzip-4.0.1.tgz#deaaccdfd14aeaf85578f733ae8210f9b4848f69" - dependencies: - file-type "^3.8.0" - get-stream "^2.2.0" - pify "^2.3.0" - yauzl "^2.4.2" - -decompress@^4.0.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/decompress/-/decompress-4.2.0.tgz#7aedd85427e5a92dacfe55674a7c505e96d01f9d" - dependencies: - decompress-tar "^4.0.0" - decompress-tarbz2 "^4.0.0" - decompress-targz "^4.0.0" - decompress-unzip "^4.0.1" - graceful-fs "^4.1.10" - make-dir "^1.0.0" - pify "^2.3.0" - strip-dirs "^2.0.0" + integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= deep-extend@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" + integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== + +define-properties@^1.1.2: + version "1.1.3" + resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" + integrity sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ== + dependencies: + object-keys "^1.0.12" define-property@^0.2.5: version "0.2.5" resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" + integrity sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY= dependencies: is-descriptor "^0.1.0" define-property@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" + integrity sha1-dp66rz9KY6rTr56NMEybvnm/sOY= dependencies: is-descriptor "^1.0.0" define-property@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" + integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== dependencies: is-descriptor "^1.0.2" isobject "^3.0.1" @@ -1739,71 +1730,44 @@ define-property@^2.0.2: delayed-stream@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= delegates@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" - -depd@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.1.tgz#5783b4e1c459f06fa5ca27f991f3d06e7a310359" - -depd@~1.1.1, depd@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" + integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o= des.js@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/des.js/-/des.js-1.0.0.tgz#c074d2e2aa6a8a9a07dbd61f9a15c2cd83ec8ecc" + integrity sha1-wHTS4qpqipoH29YfmhXCzYPsjsw= dependencies: inherits "^2.0.1" minimalistic-assert "^1.0.0" -destroy@~1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/destroy/-/destroy-1.0.4.tgz#978857442c44749e4206613e37946205826abd80" - -detect-indent@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208" - dependencies: - repeating "^2.0.0" - detect-libc@^1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-1.0.3.tgz#fa137c4bd698edf55cd5cd02ac559f91a4c4ba9b" + integrity sha1-+hN8S9aY7fVc1c0CrFWfkaTEups= diffie-hellman@^5.0.0: version "5.0.3" resolved "https://registry.yarnpkg.com/diffie-hellman/-/diffie-hellman-5.0.3.tgz#40e8ee98f55a2149607146921c63e1ae5f3d2875" + integrity sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg== dependencies: bn.js "^4.1.0" miller-rabin "^4.0.0" randombytes "^2.0.0" -dom-walk@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/dom-walk/-/dom-walk-0.1.1.tgz#672226dc74c8f799ad35307df936aba11acd6018" - domain-browser@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/domain-browser/-/domain-browser-1.2.0.tgz#3d31f50191a6749dd1375a7f522e823d42e54eda" - -drbg.js@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/drbg.js/-/drbg.js-1.0.1.tgz#3e36b6c42b37043823cdbc332d58f31e2445480b" - dependencies: - browserify-aes "^1.0.6" - create-hash "^1.1.2" - create-hmac "^1.1.4" - -duplexer3@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2" + integrity sha512-jnjyiM6eRyZl2H+W8Q/zLMA481hzi0eszAaBUzIVnmYVDBbnLxVNnfu1HgEBvCbL+71FrxMl3E6lpKH7Ge3OXA== duplexify@^3.4.2, duplexify@^3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.6.0.tgz#592903f5d80b38d037220541264d69a198fb3410" + version "3.7.1" + resolved "https://registry.yarnpkg.com/duplexify/-/duplexify-3.7.1.tgz#2a4df5317f6ccfd91f86d6fd25d8d8a103b88309" + integrity sha512-07z8uv2wMyS51kKhD1KsdXJg5WQ6t93RneqRxUHnskXVtlYYkLqM0gqStQZ3pj073g687jPCHrqNfCzawLYh5g== dependencies: end-of-stream "^1.0.0" inherits "^2.0.1" @@ -1813,34 +1777,25 @@ duplexify@^3.4.2, duplexify@^3.6.0: ecc-jsbn@~0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz#3a83a904e54353287874c564b7549386849a98c9" + integrity sha1-OoOpBOVDUyh4dMVkt1SThoSamMk= dependencies: jsbn "~0.1.0" safer-buffer "^2.1.0" -ee-first@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" - -electron-to-chromium@^1.3.30: - version "1.3.64" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.64.tgz#39f5a93bf84ab7e10cfbb7522ccfc3f1feb756cf" +electron-to-chromium@^1.3.113: + version "1.3.113" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.3.113.tgz#b1ccf619df7295aea17bc6951dc689632629e4a9" + integrity sha512-De+lPAxEcpxvqPTyZAXELNpRZXABRxf+uL/rSykstQhzj/B0l1150G/ExIIxKc16lI89Hgz81J0BHAcbTqK49g== elegant-spinner@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/elegant-spinner/-/elegant-spinner-1.0.1.tgz#db043521c95d7e303fd8f345bedc3349cfb0729e" + integrity sha1-2wQ1IcldfjA/2PNFvtwzSc+wcp4= -elliptic@6.3.3: - version "6.3.3" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.3.3.tgz#5482d9646d54bcb89fd7d994fc9e2e9568876e3f" - dependencies: - bn.js "^4.4.0" - brorand "^1.0.1" - hash.js "^1.0.0" - inherits "^2.0.1" - -elliptic@^6.0.0, elliptic@^6.2.3, elliptic@^6.4.0: +elliptic@^6.0.0: version "6.4.1" resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.4.1.tgz#c2d0b7776911b86722c632c3c06c60f2f819939a" + integrity sha512-BsXLz5sqX8OHcsh7CqBMztyXARmGQ3LWPtGjJi6DiJHq5C/qvi9P3OqgswKSDftbu8+IoI/QDTAm2fFnQ9SZSQ== dependencies: bn.js "^4.4.0" brorand "^1.0.1" @@ -1853,20 +1808,19 @@ elliptic@^6.0.0, elliptic@^6.2.3, elliptic@^6.4.0: emojis-list@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/emojis-list/-/emojis-list-2.1.0.tgz#4daa4d9db00f9819880c79fa457ae5b09a1fd389" - -encodeurl@~1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" + integrity sha1-TapNnbAPmBmIDHn6RXrlsJof04k= end-of-stream@^1.0.0, end-of-stream@^1.1.0: version "1.4.1" resolved "https://registry.yarnpkg.com/end-of-stream/-/end-of-stream-1.4.1.tgz#ed29634d19baba463b6ce6b80a37213eab71ec43" + integrity sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q== dependencies: once "^1.4.0" enhanced-resolve@^4.0.0, enhanced-resolve@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-4.1.0.tgz#41c7e0bfdfe74ac1ffe1e57ad6a5c6c9f3742a7f" + integrity sha512-F/7vkyTtyc/llOIn8oWclcB25KdRaiPBpZYDgJHgh/UHtpgT2p2eldQgtQnLtUvfMKPKxbRaQM/hHkvLHt1Vng== dependencies: graceful-fs "^4.1.2" memory-fs "^0.4.0" @@ -1875,20 +1829,40 @@ enhanced-resolve@^4.0.0, enhanced-resolve@^4.1.0: errno@^0.1.3, errno@~0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.7.tgz#4684d71779ad39af177e3f007996f7c67c852618" + integrity sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg== dependencies: prr "~1.0.1" -escape-html@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" +es-abstract@^1.5.1: + version "1.13.0" + resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.13.0.tgz#ac86145fdd5099d8dd49558ccba2eaf9b88e24e9" + integrity sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg== + dependencies: + es-to-primitive "^1.2.0" + function-bind "^1.1.1" + has "^1.0.3" + is-callable "^1.1.4" + is-regex "^1.0.4" + object-keys "^1.0.12" + +es-to-primitive@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.0.tgz#edf72478033456e8dda8ef09e00ad9650707f377" + integrity sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg== + dependencies: + is-callable "^1.1.4" + is-date-object "^1.0.1" + is-symbol "^1.0.2" escape-string-regexp@^1.0.2, escape-string-regexp@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + integrity sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ= eslint-scope@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.0.tgz#50bf3071e9338bcdc43331794a0cb533f0136172" + version "4.0.2" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.2.tgz#5f10cd6cabb1965bf479fa65745673439e21cb0e" + integrity sha512-5q1+B/ogmHl8+paxtOKx38Z8LtWkVGuNt3+GQNErqwLl6ViNp/gdJGMCjZNxZ8j/VYjDNZ2Fo+eQc1TAVPIzbg== dependencies: esrecurse "^4.1.0" estraverse "^4.1.1" @@ -1896,128 +1870,29 @@ eslint-scope@^4.0.0: esrecurse@^4.1.0: version "4.2.1" resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.2.1.tgz#007a3b9fdbc2b3bb87e4879ea19c92fdbd3942cf" + integrity sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ== dependencies: estraverse "^4.1.0" estraverse@^4.1.0, estraverse@^4.1.1: version "4.2.0" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" + integrity sha1-De4/7TH81GlhjOc0IJn8GvoL2xM= esutils@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" + integrity sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs= -etag@~1.8.1: - version "1.8.1" - resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" - -eth-ens-namehash@2.0.8: - version "2.0.8" - resolved "https://registry.yarnpkg.com/eth-ens-namehash/-/eth-ens-namehash-2.0.8.tgz#229ac46eca86d52e0c991e7cb2aef83ff0f68bcf" - dependencies: - idna-uts46-hx "^2.3.1" - js-sha3 "^0.5.7" - -eth-lib@0.1.27, eth-lib@^0.1.26: - version "0.1.27" - resolved "https://registry.yarnpkg.com/eth-lib/-/eth-lib-0.1.27.tgz#f0b0fd144f865d2d6bf8257a40004f2e75ca1dd6" - dependencies: - bn.js "^4.11.6" - elliptic "^6.4.0" - keccakjs "^0.2.1" - nano-json-stream-parser "^0.1.2" - servify "^0.1.12" - ws "^3.0.0" - xhr-request-promise "^0.1.2" - -eth-lib@0.2.7: - version "0.2.7" - resolved "https://registry.yarnpkg.com/eth-lib/-/eth-lib-0.2.7.tgz#2f93f17b1e23aec3759cd4a3fe20c1286a3fc1ca" - dependencies: - bn.js "^4.11.6" - elliptic "^6.4.0" - xhr-request-promise "^0.1.2" - -eth-sig-util@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/eth-sig-util/-/eth-sig-util-2.1.0.tgz#33e60e5486897a2ddeb4bf5a0993b2c6d5cc9e19" - dependencies: - buffer "^5.2.1" - elliptic "^6.4.0" - ethereumjs-abi "0.6.5" - ethereumjs-util "^5.1.1" - tweetnacl "^1.0.0" - tweetnacl-util "^0.15.0" - -ethereumjs-abi@0.6.5: - version "0.6.5" - resolved "https://registry.yarnpkg.com/ethereumjs-abi/-/ethereumjs-abi-0.6.5.tgz#5a637ef16ab43473fa72a29ad90871405b3f5241" - dependencies: - bn.js "^4.10.0" - ethereumjs-util "^4.3.0" - -ethereumjs-util@^4.3.0: - version "4.5.0" - resolved "http://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-4.5.0.tgz#3e9428b317eebda3d7260d854fddda954b1f1bc6" - dependencies: - bn.js "^4.8.0" - create-hash "^1.1.2" - keccakjs "^0.2.0" - rlp "^2.0.0" - secp256k1 "^3.0.1" - -ethereumjs-util@^5.1.1: - version "5.2.0" - resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-5.2.0.tgz#3e0c0d1741471acf1036052d048623dee54ad642" - dependencies: - bn.js "^4.11.0" - create-hash "^1.1.2" - ethjs-util "^0.1.3" - keccak "^1.0.2" - rlp "^2.0.0" - safe-buffer "^5.1.1" - secp256k1 "^3.0.1" - -ethers@4.0.0-beta.1: - version "4.0.0-beta.1" - resolved "https://registry.yarnpkg.com/ethers/-/ethers-4.0.0-beta.1.tgz#0648268b83e0e91a961b1af971c662cdf8cbab6d" - dependencies: - "@types/node" "^10.3.2" - aes-js "3.0.0" - bn.js "^4.4.0" - elliptic "6.3.3" - hash.js "1.1.3" - js-sha3 "0.5.7" - scrypt-js "2.0.3" - setimmediate "1.0.4" - uuid "2.0.1" - xmlhttprequest "1.8.0" - -ethjs-unit@0.1.6: - version "0.1.6" - resolved "https://registry.yarnpkg.com/ethjs-unit/-/ethjs-unit-0.1.6.tgz#c665921e476e87bce2a9d588a6fe0405b2c41699" - dependencies: - bn.js "4.11.6" - number-to-bn "1.7.0" - -ethjs-util@^0.1.3: - version "0.1.6" - resolved "https://registry.yarnpkg.com/ethjs-util/-/ethjs-util-0.1.6.tgz#f308b62f185f9fe6237132fb2a9818866a5cd536" - dependencies: - is-hex-prefixed "1.0.0" - strip-hex-prefix "1.0.0" - -eventemitter3@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-1.1.1.tgz#47786bdaa087caf7b1b75e73abc5c7d540158cd0" - -events@^1.0.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/events/-/events-1.1.1.tgz#9ebdb7635ad099c70dcc4c2a1f5004288e8bd924" +events@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/events/-/events-3.0.0.tgz#9a0a0dfaf62893d92b875b8f2698ca4114973e88" + integrity sha512-Dc381HFWJzEOhQ+d8pkNon++bk9h6cdAoAj4iE6Q4y6xgTzySWXlKn05/TVNpjnfRqi/X0EpJEJohPjNI3zpVA== evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz#7fcbdb198dc71959432efe13842684e0525acb02" + integrity sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA== dependencies: md5.js "^1.3.4" safe-buffer "^5.1.1" @@ -2025,6 +1900,7 @@ evp_bytestokey@^1.0.0, evp_bytestokey@^1.0.3: execa@0.10.0: version "0.10.0" resolved "https://registry.yarnpkg.com/execa/-/execa-0.10.0.tgz#ff456a8f53f90f8eccc71a96d11bdfc7f082cb50" + integrity sha512-7XOMnz8Ynx1gGo/3hyV9loYNPWM94jG3+3T3Y8tsfSstFmETmENCMU/A/zj8Lyaj1lkgEepKepvd6240tBRvlw== dependencies: cross-spawn "^6.0.0" get-stream "^3.0.0" @@ -2037,16 +1913,19 @@ execa@0.10.0: executable@4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/executable/-/executable-4.1.1.tgz#41532bff361d3e57af4d763b70582db18f5d133c" + integrity sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg== dependencies: pify "^2.2.0" exit-hook@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/exit-hook/-/exit-hook-1.1.1.tgz#f05ca233b48c05d54fff07765df8507e95c02ff8" + integrity sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g= expand-brackets@^2.1.4: version "2.1.4" resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" + integrity sha1-t3c14xXOMPa27/D4OwQVGiJEliI= dependencies: debug "^2.3.3" define-property "^0.2.5" @@ -2056,61 +1935,30 @@ expand-brackets@^2.1.4: snapdragon "^0.8.1" to-regex "^3.0.1" -express@^4.14.0: - version "4.16.3" - resolved "http://registry.npmjs.org/express/-/express-4.16.3.tgz#6af8a502350db3246ecc4becf6b5a34d22f7ed53" - dependencies: - accepts "~1.3.5" - array-flatten "1.1.1" - body-parser "1.18.2" - content-disposition "0.5.2" - content-type "~1.0.4" - cookie "0.3.1" - cookie-signature "1.0.6" - debug "2.6.9" - depd "~1.1.2" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - finalhandler "1.1.1" - fresh "0.5.2" - merge-descriptors "1.0.1" - methods "~1.1.2" - on-finished "~2.3.0" - parseurl "~1.3.2" - path-to-regexp "0.1.7" - proxy-addr "~2.0.3" - qs "6.5.1" - range-parser "~1.2.0" - safe-buffer "5.1.1" - send "0.16.2" - serve-static "1.13.2" - setprototypeof "1.1.0" - statuses "~1.4.0" - type-is "~1.6.16" - utils-merge "1.0.1" - vary "~1.1.2" - extend-shallow@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" + integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= dependencies: is-extendable "^0.1.0" extend-shallow@^3.0.0, extend-shallow@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" + integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg= dependencies: assign-symbols "^1.0.0" is-extendable "^1.0.1" -extend@~3.0.1, extend@~3.0.2: +extend@~3.0.1: version "3.0.2" resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa" + integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g== extglob@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" + integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== dependencies: array-unique "^0.3.2" define-property "^1.0.0" @@ -2124,6 +1972,7 @@ extglob@^2.0.4: extract-zip@1.6.6: version "1.6.6" resolved "https://registry.yarnpkg.com/extract-zip/-/extract-zip-1.6.6.tgz#1290ede8d20d0872b429fd3f351ca128ec5ef85c" + integrity sha1-EpDt6NINCHK0Kf0/NRyhKOxe+Fw= dependencies: concat-stream "1.6.0" debug "2.6.9" @@ -2133,176 +1982,136 @@ extract-zip@1.6.6: extsprintf@1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.3.0.tgz#96918440e3041a7a414f8c52e3c574eb3c3e1e05" + integrity sha1-lpGEQOMEGnpBT4xS48V06zw+HgU= extsprintf@^1.2.0: version "1.4.0" resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.0.tgz#e2689f8f356fad62cca65a3a91c5df5f9551692f" + integrity sha1-4mifjzVvrWLMplo6kcXfX5VRaS8= fast-deep-equal@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz#c053477817c86b51daa853c81e059b733d023614" + integrity sha1-wFNHeBfIa1HaqFPIHgWbcz0CNhQ= fast-deep-equal@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz#7b05218ddf9667bf7f370bf7fdb2cb15fdd0aa49" + integrity sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk= fast-json-stable-stringify@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" + integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I= fd-slicer@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.0.1.tgz#8b5bcbd9ec327c5041bf9ab023fd6750f1177e65" + integrity sha1-i1vL2ewyfFBBv5qwI/1nUPEXfmU= dependencies: pend "~1.2.0" -fd-slicer@~1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/fd-slicer/-/fd-slicer-1.1.0.tgz#25c7c89cb1f9077f8891bbe61d8f390eae256f1e" - dependencies: - pend "~1.2.0" +figgy-pudding@^3.5.1: + version "3.5.1" + resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.1.tgz#862470112901c727a0e495a80744bd5baa1d6790" + integrity sha512-vNKxJHTEKNThjfrdJwHc7brvM6eVevuO5nTj6ez8ZQ1qbXTvGthucRF7S4vf2cr71QVnT70V34v0S1DyQsti0w== figures@^1.7.0: version "1.7.0" resolved "https://registry.yarnpkg.com/figures/-/figures-1.7.0.tgz#cbe1e3affcf1cd44b80cadfed28dc793a9701d2e" + integrity sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4= dependencies: escape-string-regexp "^1.0.5" object-assign "^4.1.0" -file-type@^3.8.0: - version "3.9.0" - resolved "https://registry.yarnpkg.com/file-type/-/file-type-3.9.0.tgz#257a078384d1db8087bc449d107d52a52672b9e9" - -file-type@^5.2.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/file-type/-/file-type-5.2.0.tgz#2ddbea7c73ffe36368dfae49dc338c058c2b8ad6" - -file-type@^6.1.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/file-type/-/file-type-6.2.0.tgz#e50cd75d356ffed4e306dc4f5bcf52a79903a919" - fill-range@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" + integrity sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc= dependencies: extend-shallow "^2.0.1" is-number "^3.0.0" repeat-string "^1.6.1" to-regex-range "^2.1.0" -finalhandler@1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.1.1.tgz#eebf4ed840079c83f4249038c9d703008301b105" - dependencies: - debug "2.6.9" - encodeurl "~1.0.2" - escape-html "~1.0.3" - on-finished "~2.3.0" - parseurl "~1.3.2" - statuses "~1.4.0" - unpipe "~1.0.0" - -find-cache-dir@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-1.0.0.tgz#9288e3e9e3cc3748717d39eade17cf71fc30ee6f" +find-cache-dir@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.0.0.tgz#4c1faed59f45184530fb9d7fa123a4d04a98472d" + integrity sha512-LDUY6V1Xs5eFskUVYtIwatojt6+9xC9Chnlk/jYOOvn3FAFfSaWddxahDGyNHh0b2dMXa6YW2m0tk8TdVaXHlA== dependencies: commondir "^1.0.1" make-dir "^1.0.0" - pkg-dir "^2.0.0" + pkg-dir "^3.0.0" -find-up@^2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" +find-up@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-3.0.0.tgz#49169f1d7993430646da61ecc5ae355c21c97b73" + integrity sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg== dependencies: - locate-path "^2.0.0" + locate-path "^3.0.0" flush-write-stream@^1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.0.3.tgz#c5d586ef38af6097650b49bc41b55fabb19f35bd" + version "1.1.1" + resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8" + integrity sha512-3Z4XhFZ3992uIq0XOqb9AreonueSYphE6oYbpt5+3u06JWklbsPkNv3ZKkP9Bz/r+1MWCaMoSQ28P85+1Yc77w== dependencies: - inherits "^2.0.1" - readable-stream "^2.0.4" - -for-each@^0.3.2: - version "0.3.3" - resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e" - dependencies: - is-callable "^1.1.3" + inherits "^2.0.3" + readable-stream "^2.3.6" for-in@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" + integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= forever-agent@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE= -form-data@~2.3.1, form-data@~2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.2.tgz#4970498be604c20c005d4f5c23aecd21d6b49099" +form-data@~2.3.1: + version "2.3.3" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.3.tgz#dcce52c05f644f298c6a7ab936bd724ceffbf3a6" + integrity sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ== dependencies: asynckit "^0.4.0" - combined-stream "1.0.6" + combined-stream "^1.0.6" mime-types "^2.1.12" -forwarded@~0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" - fragment-cache@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" + integrity sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk= dependencies: map-cache "^0.2.2" -fresh@0.5.2: - version "0.5.2" - resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" - from2@^2.1.0: version "2.3.0" resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af" + integrity sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8= dependencies: inherits "^2.0.1" readable-stream "^2.0.0" -fs-constants@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" - fs-extra@4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-4.0.1.tgz#7fc0c6c8957f983f57f306a24e5b9ddd8d0dd880" + integrity sha1-f8DGyJV/mD9X8waiTlud3Y0N2IA= dependencies: graceful-fs "^4.1.2" jsonfile "^3.0.0" universalify "^0.1.0" -fs-extra@^2.0.0, fs-extra@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-2.1.2.tgz#046c70163cef9aad46b0e4a7fa467fb22d71de35" - dependencies: - graceful-fs "^4.1.2" - jsonfile "^2.1.0" - fs-minipass@^1.2.5: version "1.2.5" resolved "https://registry.yarnpkg.com/fs-minipass/-/fs-minipass-1.2.5.tgz#06c277218454ec288df77ada54a03b8702aacb9d" + integrity sha512-JhBl0skXjUPCFH7x6x61gQxrKyXsxB5gcgePLZCwfyCGGsTISMoIeObbrvVeP6Xmyaudw4TT43qV2Gz+iyd2oQ== dependencies: minipass "^2.2.1" -fs-promise@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/fs-promise/-/fs-promise-2.0.3.tgz#f64e4f854bcf689aa8bddcba268916db3db46854" - dependencies: - any-promise "^1.3.0" - fs-extra "^2.0.0" - mz "^2.6.0" - thenify-all "^1.6.0" - fs-write-stream-atomic@^1.0.8: version "1.0.10" resolved "https://registry.yarnpkg.com/fs-write-stream-atomic/-/fs-write-stream-atomic-1.0.10.tgz#b47df53493ef911df75731e70a9ded0189db40c9" + integrity sha1-tH31NJPvkR33VzHnCp3tAYnbQMk= dependencies: graceful-fs "^4.1.2" iferr "^0.1.5" @@ -2312,26 +2121,25 @@ fs-write-stream-atomic@^1.0.8: fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= -fsevents@^1.2.2: - version "1.2.4" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.4.tgz#f41dcb1af2582af3692da36fc55cbd8e1041c426" +fsevents@^1.2.7: + version "1.2.7" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.2.7.tgz#4851b664a3783e52003b3c66eb0eee1074933aa4" + integrity sha512-Pxm6sI2MeBD7RdD12RYsqaP0nMiwx8eZBXCa6z2L+mRHm2DYrOYwihmhjpkdjUHwQhslWQjRpEgNq4XvBmaAuw== dependencies: nan "^2.9.2" node-pre-gyp "^0.10.0" -fstream@^1.0.2, fstream@^1.0.8: - version "1.0.11" - resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.11.tgz#5c1fb1f117477114f0632a0eb4b71b3cb0fd3171" - dependencies: - graceful-fs "^4.1.2" - inherits "~2.0.0" - mkdirp ">=0.5 0" - rimraf "2" +function-bind@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" + integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== gauge@~2.7.3: version "2.7.4" resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" + integrity sha1-LANAXHU4w51+s3sxcCLjJfsBi/c= dependencies: aproba "^1.0.3" console-control-strings "^1.0.0" @@ -2342,36 +2150,34 @@ gauge@~2.7.3: strip-ansi "^3.0.1" wide-align "^1.1.0" -get-stream@^2.2.0: - version "2.3.1" - resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-2.3.1.tgz#5f38f93f346009666ee0150a054167f91bdd95de" - dependencies: - object-assign "^4.0.1" - pinkie-promise "^2.0.0" - get-stream@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14" + integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ= get-value@^2.0.3, get-value@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" + integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= getos@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/getos/-/getos-3.1.0.tgz#db3aa4df15a3295557ce5e81aa9e3e5cdfaa6567" + integrity sha512-i9vrxtDu5DlLVFcrbqUqGWYlZN/zZ4pGMICCAcZoYsX3JA54nYp8r5EThw5K+m2q3wszkx4Th746JstspB0H4Q== dependencies: async "2.4.0" getpass@^0.1.1: version "0.1.7" resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" + integrity sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo= dependencies: assert-plus "^1.0.0" glob-parent@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-3.1.0.tgz#9e6af6299d8d3bd2bd40430832bd113df906c5ae" + integrity sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4= dependencies: is-glob "^3.1.0" path-dirname "^1.0.0" @@ -2379,6 +2185,7 @@ glob-parent@^3.1.0: glob@7.1.2: version "7.1.2" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" + integrity sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ== dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" @@ -2387,9 +2194,10 @@ glob@7.1.2: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.0.5, glob@^7.1.2: +glob@^7.1.3: version "7.1.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" + integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== dependencies: fs.realpath "^1.0.0" inflight "^1.0.4" @@ -2401,96 +2209,64 @@ glob@^7.0.5, glob@^7.1.2: global-dirs@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-0.1.1.tgz#b319c0dd4607f353f3be9cca4c72fc148c49f445" + integrity sha1-sxnA3UYH81PzvpzKTHL8FIxJ9EU= dependencies: ini "^1.3.4" -global@~4.3.0: - version "4.3.2" - resolved "https://registry.yarnpkg.com/global/-/global-4.3.2.tgz#e76989268a6c74c38908b1305b10fc0e394e9d0f" - dependencies: - min-document "^2.19.0" - process "~0.5.1" +globals@^11.1.0: + version "11.11.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-11.11.0.tgz#dcf93757fa2de5486fbeed7118538adf789e9c2e" + integrity sha512-WHq43gS+6ufNOEqlrDBxVEbb8ntfXrfAUU2ZOpCxrBdGKW3gyv8mCxAfIBD0DroPKGrJ2eSsXsLtY9MPntsyTw== -globals@^9.18.0: - version "9.18.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" - -got@7.1.0, got@^7.1.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/got/-/got-7.1.0.tgz#05450fd84094e6bbea56f451a43a9c289166385a" - dependencies: - decompress-response "^3.2.0" - duplexer3 "^0.1.4" - get-stream "^3.0.0" - is-plain-obj "^1.1.0" - is-retry-allowed "^1.0.0" - is-stream "^1.0.0" - isurl "^1.0.0-alpha5" - lowercase-keys "^1.0.0" - p-cancelable "^0.3.0" - p-timeout "^1.1.1" - safe-buffer "^5.0.1" - timed-out "^4.0.0" - url-parse-lax "^1.0.0" - url-to-options "^1.0.1" - -graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.6: - version "4.1.11" - resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" - -"graceful-readlink@>= 1.0.0": - version "1.0.1" - resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" +graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6: + version "4.1.15" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00" + integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA== har-schema@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92" + integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI= har-validator@~5.0.3: version "5.0.3" resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.0.3.tgz#ba402c266194f15956ef15e0fcf242993f6a7dfd" + integrity sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0= dependencies: ajv "^5.1.0" har-schema "^2.0.0" -har-validator@~5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.0.tgz#44657f5688a22cfd4b72486e81b3a3fb11742c29" - dependencies: - ajv "^5.3.0" - har-schema "^2.0.0" - has-ansi@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" + integrity sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE= dependencies: ansi-regex "^2.0.0" has-flag@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-2.0.0.tgz#e8207af1cc7b30d446cc70b734b5e8be18f88d51" + integrity sha1-6CB68cx7MNRGzHC3NLXovhj4jVE= has-flag@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd" + integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0= -has-symbol-support-x@^1.4.1: - version "1.4.2" - resolved "https://registry.yarnpkg.com/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz#1409f98bc00247da45da67cee0a36f282ff26455" - -has-to-string-tag-x@^1.2.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz#a045ab383d7b4b2012a00148ab0aa5f290044d4d" - dependencies: - has-symbol-support-x "^1.4.1" +has-symbols@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.0.tgz#ba1a8f1af2a0fc39650f5c850367704122063b44" + integrity sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q= has-unicode@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + integrity sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk= has-value@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" + integrity sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8= dependencies: get-value "^2.0.3" has-values "^0.1.4" @@ -2499,6 +2275,7 @@ has-value@^0.3.1: has-value@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" + integrity sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc= dependencies: get-value "^2.0.6" has-values "^1.0.0" @@ -2507,31 +2284,35 @@ has-value@^1.0.0: has-values@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" + integrity sha1-bWHeldkd/Km5oCCJrThL/49it3E= has-values@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" + integrity sha1-lbC2P+whRmGab+V/51Yo1aOe/k8= dependencies: is-number "^3.0.0" kind-of "^4.0.0" +has@^1.0.1, has@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" + integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== + dependencies: + function-bind "^1.1.1" + hash-base@^3.0.0: version "3.0.4" resolved "https://registry.yarnpkg.com/hash-base/-/hash-base-3.0.4.tgz#5fc8686847ecd73499403319a6b0a3f3f6ae4918" + integrity sha1-X8hoaEfs1zSZQDMZprCj8/auSRg= dependencies: inherits "^2.0.1" safe-buffer "^5.0.1" -hash.js@1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.3.tgz#340dedbe6290187151c1ea1d777a3448935df846" - dependencies: - inherits "^2.0.3" - minimalistic-assert "^1.0.0" - hash.js@^1.0.0, hash.js@^1.0.3: - version "1.1.5" - resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.5.tgz#e38ab4b85dfb1e0c40fe9265c0e9b54854c23812" + version "1.1.7" + resolved "https://registry.yarnpkg.com/hash.js/-/hash.js-1.1.7.tgz#0babca538e8d4ee4a0f8988d68866537a003cf42" + integrity sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA== dependencies: inherits "^2.0.3" minimalistic-assert "^1.0.1" @@ -2539,43 +2320,16 @@ hash.js@^1.0.0, hash.js@^1.0.3: hmac-drbg@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/hmac-drbg/-/hmac-drbg-1.0.1.tgz#d2745701025a6c775a6c545793ed502fc0c649a1" + integrity sha1-0nRXAQJabHdabFRXk+1QL8DGSaE= dependencies: hash.js "^1.0.3" minimalistic-assert "^1.0.0" minimalistic-crypto-utils "^1.0.1" -home-or-tmp@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8" - dependencies: - os-homedir "^1.0.0" - os-tmpdir "^1.0.1" - -http-errors@1.6.2: - version "1.6.2" - resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.2.tgz#0a002cc85707192a7e7946ceedc11155f60ec736" - dependencies: - depd "1.1.1" - inherits "2.0.3" - setprototypeof "1.0.3" - statuses ">= 1.3.1 < 2" - -http-errors@1.6.3, http-errors@~1.6.2, http-errors@~1.6.3: - version "1.6.3" - resolved "http://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" - dependencies: - depd "~1.1.2" - inherits "2.0.3" - setprototypeof "1.1.0" - statuses ">= 1.4.0 < 2" - -http-https@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/http-https/-/http-https-1.0.0.tgz#2f908dd5f1db4068c058cd6e6d4ce392c913389b" - http-signature@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" + integrity sha1-muzZJRFHcvPZW2WmCruPfBj7rOE= dependencies: assert-plus "^1.0.0" jsprim "^1.2.2" @@ -2584,137 +2338,145 @@ http-signature@~1.2.0: https-browserify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/https-browserify/-/https-browserify-1.0.0.tgz#ec06c10e0a34c0f2faf199f7fd7fc78fffd03c73" - -iconv-lite@0.4.19: - version "0.4.19" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.19.tgz#f7468f60135f5e5dad3399c0a81be9a1603a082b" - -iconv-lite@0.4.23: - version "0.4.23" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.23.tgz#297871f63be507adcfbfca715d0cd0eed84e9a63" - dependencies: - safer-buffer ">= 2.1.2 < 3" + integrity sha1-7AbBDgo0wPL68Zn3/X/Hj//QPHM= iconv-lite@^0.4.4: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== dependencies: safer-buffer ">= 2.1.2 < 3" -idna-uts46-hx@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/idna-uts46-hx/-/idna-uts46-hx-2.3.1.tgz#a1dc5c4df37eee522bf66d969cc980e00e8711f9" - dependencies: - punycode "2.1.0" - -ieee754@^1.1.11, ieee754@^1.1.4: +ieee754@^1.1.4: version "1.1.12" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.1.12.tgz#50bf24e5b9c8bb98af4964c941cdb0918da7b60b" + integrity sha512-GguP+DRY+pJ3soyIiGPTvdiVXjZ+DbXOxGpXn3eMvNW4x4irjqXm4wHKscC+TfxSJ0yw/S1F24tqdMNsMZTiLA== iferr@^0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/iferr/-/iferr-0.1.5.tgz#c60eed69e6d8fdb6b3104a1fcbca1c192dc5b501" + integrity sha1-xg7taebY/bazEEofy8ocGS3FtQE= ignore-walk@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/ignore-walk/-/ignore-walk-3.0.1.tgz#a83e62e7d272ac0e3b551aaa82831a19b69f82f8" + integrity sha512-DTVlMx3IYPe0/JJcYP7Gxg7ttZZu3IInhuEhbchuqneY9wWe5Ojy2mXLBaQFUQmo0AW2r3qG7m1mg86js+gnlQ== dependencies: minimatch "^3.0.4" imurmurhash@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + integrity sha1-khi5srkoojixPcT7a21XbyMUU+o= indent-string@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-2.1.0.tgz#8e2d48348742121b4a8218b7a137e9a52049dc80" + integrity sha1-ji1INIdCEhtKghi3oTfppSBJ3IA= dependencies: repeating "^2.0.0" indent-string@^3.0.0: version "3.2.0" resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-3.2.0.tgz#4a5fd6d27cc332f37e5419a504dbb837105c9289" + integrity sha1-Sl/W0nzDMvN+VBmlBNu4NxBckok= indexof@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/indexof/-/indexof-0.0.1.tgz#82dc336d232b9062179d05ab3293a66059fd435d" + integrity sha1-gtwzbSMrkGIXnQWrMpOmYFn9Q10= inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= dependencies: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1, inherits@~2.0.3: +inherits@2, inherits@2.0.3, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.1, inherits@~2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4= inherits@2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" + integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE= ini@^1.3.4, ini@~1.3.0: version "1.3.5" resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.5.tgz#eee25f56db1c9ec6085e0c22778083f596abf927" + integrity sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw== invariant@^2.2.2: version "2.2.4" resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6" + integrity sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA== dependencies: loose-envify "^1.0.0" -ipaddr.js@1.8.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.8.0.tgz#eaa33d6ddd7ace8f7f6fe0c9ca0440e706738b1e" - is-accessor-descriptor@^0.1.6: version "0.1.6" resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" + integrity sha1-qeEss66Nh2cn7u84Q/igiXtcmNY= dependencies: kind-of "^3.0.2" is-accessor-descriptor@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" + integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ== dependencies: kind-of "^6.0.0" is-binary-path@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" + integrity sha1-dfFmQrSA8YenEcgUFh/TpKdlWJg= dependencies: binary-extensions "^1.0.0" is-buffer@^1.1.5: version "1.1.6" resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" + integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== -is-callable@^1.1.3: +is-callable@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.1.4.tgz#1e1adf219e1eeb684d691f9d6a05ff0d30a24d75" + integrity sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA== is-ci@1.0.10: version "1.0.10" - resolved "http://registry.npmjs.org/is-ci/-/is-ci-1.0.10.tgz#f739336b2632365061a9d48270cd56ae3369318e" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.0.10.tgz#f739336b2632365061a9d48270cd56ae3369318e" + integrity sha1-9zkzayYyNlBhqdSCcM1WrjNpMY4= dependencies: ci-info "^1.0.0" is-data-descriptor@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" + integrity sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y= dependencies: kind-of "^3.0.2" is-data-descriptor@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" + integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ== dependencies: kind-of "^6.0.0" +is-date-object@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.1.tgz#9aa20eb6aeebbff77fbd33e74ca01b33581d3a16" + integrity sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY= + is-descriptor@^0.1.0: version "0.1.6" resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" + integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg== dependencies: is-accessor-descriptor "^0.1.6" is-data-descriptor "^0.1.4" @@ -2723,6 +2485,7 @@ is-descriptor@^0.1.0: is-descriptor@^1.0.0, is-descriptor@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" + integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg== dependencies: is-accessor-descriptor "^1.0.0" is-data-descriptor "^1.0.0" @@ -2731,259 +2494,262 @@ is-descriptor@^1.0.0, is-descriptor@^1.0.2: is-extendable@^0.1.0, is-extendable@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" + integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= is-extendable@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" + integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== dependencies: is-plain-object "^2.0.4" is-extglob@^2.1.0, is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= is-finite@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa" + integrity sha1-zGZ3aVYCvlUO8R6LSqYwU0K20Ko= dependencies: number-is-nan "^1.0.0" is-fullwidth-code-point@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" + integrity sha1-754xOG8DGn8NZDr4L95QxFfvAMs= dependencies: number-is-nan "^1.0.0" is-fullwidth-code-point@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" - -is-function@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-function/-/is-function-1.0.1.tgz#12cfb98b65b57dd3d193a3121f5f6e2f437602b5" + integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= is-glob@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-3.1.0.tgz#7ba5ae24217804ac70707b96922567486cc3e84a" + integrity sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo= dependencies: is-extglob "^2.1.0" is-glob@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.0.tgz#9521c76845cc2610a85203ddf080a958c2ffabc0" + integrity sha1-lSHHaEXMJhCoUgPd8ICpWML/q8A= dependencies: is-extglob "^2.1.1" -is-hex-prefixed@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz#7d8d37e6ad77e5d127148913c573e082d777f554" - is-installed-globally@0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/is-installed-globally/-/is-installed-globally-0.1.0.tgz#0dfd98f5a9111716dd535dda6492f67bf3d25a80" + integrity sha1-Df2Y9akRFxbdU13aZJL2e/PSWoA= dependencies: global-dirs "^0.1.0" is-path-inside "^1.0.0" -is-natural-number@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/is-natural-number/-/is-natural-number-4.0.1.tgz#ab9d76e1db4ced51e35de0c72ebecf09f734cde8" - is-number@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" + integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU= dependencies: kind-of "^3.0.2" -is-object@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-object/-/is-object-1.0.1.tgz#8952688c5ec2ffd6b03ecc85e769e02903083470" - is-path-inside@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.1.tgz#8ef5b7de50437a3fdca6b4e865ef7aa55cb48036" + integrity sha1-jvW33lBDej/cprToZe96pVy0gDY= dependencies: path-is-inside "^1.0.1" -is-plain-obj@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" - is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" + integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== dependencies: isobject "^3.0.1" is-promise@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" + integrity sha1-eaKp7OfwlugPNtKy87wWwf9L8/o= -is-retry-allowed@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.1.0.tgz#11a060568b67339444033d0125a61a20d564fb34" +is-regex@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.0.4.tgz#5517489b547091b0930e095654ced25ee97e9491" + integrity sha1-VRdIm1RwkbCTDglWVM7SXul+lJE= + dependencies: + has "^1.0.1" -is-stream@^1.0.0, is-stream@^1.1.0: +is-stream@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" + integrity sha1-EtSj3U5o4Lec6428hBc66A2RykQ= -is-typedarray@^1.0.0, is-typedarray@~1.0.0: +is-symbol@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.2.tgz#a055f6ae57192caee329e7a860118b497a950f38" + integrity sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw== + dependencies: + has-symbols "^1.0.0" + +is-typedarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + integrity sha1-5HnICFjfDBsR3dppQPlgEfzaSpo= is-windows@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" + integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= isobject@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" + integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk= dependencies: isarray "1.0.0" isobject@^3.0.0, isobject@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" + integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + integrity sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo= -isurl@^1.0.0-alpha5: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isurl/-/isurl-1.0.0.tgz#b27f4f49f3cdaa3ea44a0a5b7f3462e6edc39d67" - dependencies: - has-to-string-tag-x "^1.2.0" - is-object "^1.0.1" +js-levenshtein@^1.1.3: + version "1.1.6" + resolved "https://registry.yarnpkg.com/js-levenshtein/-/js-levenshtein-1.1.6.tgz#c6cee58eb3550372df8deb85fad5ce66ce01d59d" + integrity sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g== -js-sha3@0.5.7, js-sha3@^0.5.7: - version "0.5.7" - resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.5.7.tgz#0d4ffd8002d5333aabaf4a23eed2f6374c9f28e7" - -js-sha3@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.3.1.tgz#86122802142f0828502a0d1dee1d95e253bb0243" - -"js-tokens@^3.0.0 || ^4.0.0": +"js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499" - -js-tokens@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.2.tgz#9866df395102130e38f7f996bceb65443209c25b" + integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ== jsbn@~0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + integrity sha1-peZUwuWi3rXyAdls77yoDA7y9RM= -jsesc@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b" +jsesc@^2.5.1: + version "2.5.2" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" + integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA== jsesc@~0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" + integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0= json-parse-better-errors@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9" + integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw== json-schema-traverse@^0.3.0: version "0.3.1" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340" + integrity sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A= json-schema-traverse@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660" + integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg== json-schema@0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" + integrity sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM= json-stringify-safe@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= -json5@^0.5.0, json5@^0.5.1: - version "0.5.1" - resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" +json5@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-1.0.1.tgz#779fb0018604fa854eacbf6252180d83543e3dbe" + integrity sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow== + dependencies: + minimist "^1.2.0" -jsonfile@^2.1.0: - version "2.4.0" - resolved "http://registry.npmjs.org/jsonfile/-/jsonfile-2.4.0.tgz#3736a2b428b87bbda0cc83b53fa3d633a35c2ae8" - optionalDependencies: - graceful-fs "^4.1.6" +json5@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.0.tgz#e7a0c62c48285c628d20a10b85c89bb807c32850" + integrity sha512-8Mh9h6xViijj36g7Dxi+Y4S6hNGV96vcJZr/SrlHh1LR/pEn/8j/+qIBbs44YKl69Lrfctp4QD+AdWLTMqEZAQ== + dependencies: + minimist "^1.2.0" jsonfile@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-3.0.1.tgz#a5ecc6f65f53f662c4415c7675a0331d0992ec66" + integrity sha1-pezG9l9T9mLEQVx2daAzHQmS7GY= optionalDependencies: graceful-fs "^4.1.6" jsprim@^1.2.2: version "1.4.1" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" + integrity sha1-MT5mvB5cwG5Di8G3SZwuXFastqI= dependencies: assert-plus "1.0.0" extsprintf "1.3.0" json-schema "0.2.3" verror "1.10.0" -keccak@^1.0.2: - version "1.4.0" - resolved "https://registry.yarnpkg.com/keccak/-/keccak-1.4.0.tgz#572f8a6dbee8e7b3aa421550f9e6408ca2186f80" - dependencies: - bindings "^1.2.1" - inherits "^2.0.3" - nan "^2.2.1" - safe-buffer "^5.1.0" - -keccakjs@^0.2.0, keccakjs@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/keccakjs/-/keccakjs-0.2.1.tgz#1d633af907ef305bbf9f2fa616d56c44561dfa4d" - dependencies: - browserify-sha3 "^0.0.1" - sha3 "^1.1.0" - kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.2.0: version "3.2.2" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" + integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= dependencies: is-buffer "^1.1.5" kind-of@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" + integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc= dependencies: is-buffer "^1.1.5" kind-of@^5.0.0: version "5.1.0" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" + integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== kind-of@^6.0.0, kind-of@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.2.tgz#01146b36a6218e64e58f3a8d66de5d7fc6f6d051" + integrity sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA== lazy-ass@1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/lazy-ass/-/lazy-ass-1.6.0.tgz#7999655e8646c17f089fdd187d150d3324d54513" + integrity sha1-eZllXoZGwX8In90YfRUNMyTVRRM= listr-silent-renderer@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/listr-silent-renderer/-/listr-silent-renderer-1.1.1.tgz#924b5a3757153770bf1a8e3fbf74b8bbf3f9242e" + integrity sha1-kktaN1cVN3C/Go4/v3S4u/P5JC4= listr-update-renderer@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/listr-update-renderer/-/listr-update-renderer-0.2.0.tgz#ca80e1779b4e70266807e8eed1ad6abe398550f9" + integrity sha1-yoDhd5tOcCZoB+ju0a1qvjmFUPk= dependencies: chalk "^1.1.3" cli-truncate "^0.2.1" @@ -2997,6 +2763,7 @@ listr-update-renderer@^0.2.0: listr-verbose-renderer@^0.4.0: version "0.4.1" resolved "https://registry.yarnpkg.com/listr-verbose-renderer/-/listr-verbose-renderer-0.4.1.tgz#8206f4cf6d52ddc5827e5fd14989e0e965933a35" + integrity sha1-ggb0z21S3cWCfl/RSYng6WWTOjU= dependencies: chalk "^1.1.3" cli-cursor "^1.0.2" @@ -3006,6 +2773,7 @@ listr-verbose-renderer@^0.4.0: listr@0.12.0: version "0.12.0" resolved "https://registry.yarnpkg.com/listr/-/listr-0.12.0.tgz#6bce2c0f5603fa49580ea17cd6a00cc0e5fa451a" + integrity sha1-a84sD1YD+klYDqF81qAMwOX6RRo= dependencies: chalk "^1.1.3" cli-truncate "^0.2.1" @@ -3025,133 +2793,123 @@ listr@0.12.0: strip-ansi "^3.0.1" loader-runner@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.3.0.tgz#f482aea82d543e07921700d5a46ef26fdac6b8a2" + version "2.4.0" + resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357" + integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw== loader-utils@^1.0.2, loader-utils@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.1.0.tgz#c98aef488bcceda2ffb5e2de646d6a754429f5cd" + version "1.2.3" + resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7" + integrity sha512-fkpz8ejdnEMG3s37wGL07iSBDg99O9D5yflE9RGNH3hRdx9SOwYfnGYdZOUIZitN8E+E2vkq3MUMYMvPYl5ZZA== dependencies: - big.js "^3.1.3" + big.js "^5.2.2" emojis-list "^2.0.0" - json5 "^0.5.0" + json5 "^1.0.1" -locate-path@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" +locate-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-3.0.0.tgz#dbec3b3ab759758071b58fe59fc41871af21400e" + integrity sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A== dependencies: - p-locate "^2.0.0" + p-locate "^3.0.0" path-exists "^3.0.0" lodash.clonedeep@4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef" - -lodash.debounce@^4.0.8: - version "4.0.8" - resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" + integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8= lodash.once@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w= -lodash@4.17.10, lodash@^4.14.0, lodash@^4.17.4: - version "4.17.10" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.10.tgz#1b7793cf7259ea38fb3661d4d38b3260af8ae4e7" +lodash@4.17.11, lodash@^4.14.0, lodash@^4.17.10, lodash@^4.17.11: + version "4.17.11" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.11.tgz#b39ea6229ef607ecd89e2c8df12536891cac9b8d" + integrity sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg== log-symbols@2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-2.2.0.tgz#5740e1c5d6f0dfda4ad9323b5332107ef6b4c40a" + integrity sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg== dependencies: chalk "^2.0.1" log-symbols@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-1.0.2.tgz#376ff7b58ea3086a0f09facc74617eca501e1a18" + integrity sha1-N2/3tY6jCGoPCfrMdGF+ylAeGhg= dependencies: chalk "^1.0.0" log-update@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/log-update/-/log-update-1.0.2.tgz#19929f64c4093d2d2e7075a1dad8af59c296b8d1" + integrity sha1-GZKfZMQJPS0ucHWh2tivWcKWuNE= dependencies: ansi-escapes "^1.0.0" cli-cursor "^1.0.2" -long@4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/long/-/long-4.0.0.tgz#9a7b71cfb7d361a194ea555241c92f7468d5bf28" - -long@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/long/-/long-3.2.0.tgz#d821b7138ca1cb581c172990ef14db200b5c474b" - loose-envify@^1.0.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" + integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== dependencies: js-tokens "^3.0.0 || ^4.0.0" -lowercase-keys@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f" - -lru-cache@^4.1.1: - version "4.1.3" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.3.tgz#a1175cf3496dfc8436c156c334b4955992bce69c" +lru-cache@^5.1.1: + version "5.1.1" + resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920" + integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w== dependencies: - pseudomap "^1.0.2" - yallist "^2.1.2" + yallist "^3.0.2" make-dir@^1.0.0: version "1.3.0" resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-1.3.0.tgz#79c1033b80515bd6d24ec9933e860ca75ee27f0c" + integrity sha512-2w31R7SJtieJJnQtGc7RVL2StM2vGYVfqUOvUDxH6bC6aJTxPxTF0GnIgCyu7tjockiUWAYQRbxa7vKn34s5sQ== dependencies: pify "^3.0.0" mamacro@^0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/mamacro/-/mamacro-0.0.3.tgz#ad2c9576197c9f1abf308d0787865bd975a3f3e4" + integrity sha512-qMEwh+UujcQ+kbz3T6V+wAmO2U8veoq2w+3wY8MquqwVA3jChfwY+Tk52GZKDfACEPjuZ7r2oJLejwpt8jtwTA== map-cache@^0.2.2: version "0.2.2" resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" + integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= map-visit@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" + integrity sha1-7Nyo8TFE5mDxtb1B8S80edmN+48= dependencies: object-visit "^1.0.0" md5.js@^1.3.4: - version "1.3.4" - resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.4.tgz#e9bdbde94a20a5ac18b04340fc5764d5b09d901d" + version "1.3.5" + resolved "https://registry.yarnpkg.com/md5.js/-/md5.js-1.3.5.tgz#b5d07b8e3216e3e27cd728d72f70d1e6a342005f" + integrity sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg== dependencies: hash-base "^3.0.0" inherits "^2.0.1" - -media-typer@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + safe-buffer "^5.1.2" memory-fs@^0.4.0, memory-fs@~0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" + integrity sha1-OpoguEYlI+RHz7x+i7gO1me/xVI= dependencies: errno "^0.1.3" readable-stream "^2.0.1" -merge-descriptors@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" - -methods@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" - -micromatch@^3.1.4, micromatch@^3.1.8: +micromatch@^3.1.10, micromatch@^3.1.4, micromatch@^3.1.8: version "3.1.10" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" + integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== dependencies: arr-diff "^4.0.0" array-unique "^0.3.2" @@ -3170,72 +2928,69 @@ micromatch@^3.1.4, micromatch@^3.1.8: miller-rabin@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/miller-rabin/-/miller-rabin-4.0.1.tgz#f080351c865b0dc562a8462966daa53543c78a4d" + integrity sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA== dependencies: bn.js "^4.0.0" brorand "^1.0.1" -mime-db@~1.36.0: - version "1.36.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.36.0.tgz#5020478db3c7fe93aad7bbcc4dcf869c43363397" +mime-db@~1.38.0: + version "1.38.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.38.0.tgz#1a2aab16da9eb167b49c6e4df2d9c68d63d8e2ad" + integrity sha512-bqVioMFFzc2awcdJZIzR3HjZFX20QhilVS7hytkKrv7xFAn8bM1gzc/FOX2awLISvWe0PV8ptFKcon+wZ5qYkg== -mime-types@^2.1.12, mime-types@^2.1.16, mime-types@~2.1.17, mime-types@~2.1.18, mime-types@~2.1.19: - version "2.1.20" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.20.tgz#930cb719d571e903738520f8470911548ca2cc19" +mime-types@^2.1.12, mime-types@~2.1.17: + version "2.1.22" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.22.tgz#fe6b355a190926ab7698c9a0556a11199b2199bd" + integrity sha512-aGl6TZGnhm/li6F7yx82bJiBZwgiEa4Hf6CNr8YO+r5UHr53tSTYZb102zyU50DOWWKeOv0uQLRL0/9EiKWCog== dependencies: - mime-db "~1.36.0" - -mime@1.4.1: - version "1.4.1" - resolved "https://registry.yarnpkg.com/mime/-/mime-1.4.1.tgz#121f9ebc49e3766f311a76e1fa1c8003c4b03aa6" - -mimic-response@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b" - -min-document@^2.19.0: - version "2.19.0" - resolved "https://registry.yarnpkg.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685" - dependencies: - dom-walk "^0.1.0" + mime-db "~1.38.0" minimalistic-assert@^1.0.0, minimalistic-assert@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" + integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" + integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= -minimatch@^3.0.2, minimatch@^3.0.4: +minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== dependencies: brace-expansion "^1.1.7" minimist@0.0.8: version "0.0.8" - resolved "http://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" + integrity sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0= minimist@1.2.0, minimist@^1.2.0: version "1.2.0" - resolved "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" + integrity sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ= -minipass@^2.2.1, minipass@^2.3.3: - version "2.3.4" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.4.tgz#4768d7605ed6194d6d576169b9e12ef71e9d9957" +minipass@^2.2.1, minipass@^2.3.4: + version "2.3.5" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-2.3.5.tgz#cacebe492022497f656b0f0f51e2682a9ed2d848" + integrity sha512-Gi1W4k059gyRbyVUZQ4mEqLm0YIUiGYfvxhF6SIlk3ui1WVxMTGfGdQ2SInh3PDrRTVvPKgULkpJtT4RH10+VA== dependencies: safe-buffer "^5.1.2" yallist "^3.0.0" -minizlib@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.1.0.tgz#11e13658ce46bc3a70a267aac58359d1e0c29ceb" +minizlib@^1.1.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/minizlib/-/minizlib-1.2.1.tgz#dd27ea6136243c7c880684e8672bb3a45fd9b614" + integrity sha512-7+4oTUOWKg7AuL3vloEWekXY2/D20cevzsrNT2kGWm+39J9hGTCBv8VI5Pm5lXZ/o3/mdR4f8rflAPhnQb8mPA== dependencies: minipass "^2.2.1" -mississippi@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-2.0.0.tgz#3442a508fafc28500486feea99409676e4ee5a6f" +mississippi@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/mississippi/-/mississippi-3.0.0.tgz#ea0a3291f97e0b5e8776b363d5f0a12d94c67022" + integrity sha512-x471SsVjUtBRtcvd4BzKE9kFC+/2TeWgKCgw0bZcw1b9l2X3QX5vCWgF+KaZaYm87Ss//rHnWryupDrgLvmSkA== dependencies: concat-stream "^1.5.0" duplexify "^3.4.2" @@ -3243,7 +2998,7 @@ mississippi@^2.0.0: flush-write-stream "^1.0.0" from2 "^2.1.0" parallel-transform "^1.1.0" - pump "^2.0.1" + pump "^3.0.0" pumpify "^1.3.3" stream-each "^1.1.0" through2 "^2.0.0" @@ -3251,39 +3006,34 @@ mississippi@^2.0.0: mixin-deep@^1.2.0: version "1.3.1" resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.1.tgz#a49e7268dce1a0d9698e45326c5626df3543d0fe" + integrity sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ== dependencies: for-in "^1.0.2" is-extendable "^1.0.1" -mkdirp-promise@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/mkdirp-promise/-/mkdirp-promise-5.0.1.tgz#e9b8f68e552c68a9c1713b84883f7a1dd039b8a1" - dependencies: - mkdirp "*" - -mkdirp@*, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0: - version "0.5.1" - resolved "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" - dependencies: - minimist "0.0.8" - mkdirp@0.5.0: version "0.5.0" - resolved "http://registry.npmjs.org/mkdirp/-/mkdirp-0.5.0.tgz#1d73076a6df986cd9344e15e71fcc05a4c9abf12" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.0.tgz#1d73076a6df986cd9344e15e71fcc05a4c9abf12" + integrity sha1-HXMHam35hs2TROFecfzAWkyavxI= dependencies: minimist "0.0.8" -mock-fs@^4.1.0: - version "4.6.0" - resolved "https://registry.yarnpkg.com/mock-fs/-/mock-fs-4.6.0.tgz#d944ef4c3e03ceb4e8332b4b31b8ac254051c8ae" +mkdirp@^0.5.0, mkdirp@^0.5.1, mkdirp@~0.5.0: + version "0.5.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" + integrity sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM= + dependencies: + minimist "0.0.8" -mout@^0.11.0: - version "0.11.1" - resolved "https://registry.yarnpkg.com/mout/-/mout-0.11.1.tgz#ba3611df5f0e5b1ffbfd01166b8f02d1f5fa2b99" +moment@2.22.2: + version "2.22.2" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.22.2.tgz#3c257f9839fc0e93ff53149632239eb90783ff66" + integrity sha1-PCV/mDn8DpP/UxSWMiOeuQeD/2Y= move-concurrently@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/move-concurrently/-/move-concurrently-1.0.1.tgz#be2c005fda32e0b29af1f05d7c4b33214c701f92" + integrity sha1-viwAX9oy4LKa8fBdfEszIUxwH5I= dependencies: aproba "^1.1.1" copy-concurrently "^1.0.0" @@ -3295,34 +3045,22 @@ move-concurrently@^1.0.1: ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + integrity sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g= -mz@^2.6.0: - version "2.7.0" - resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" - dependencies: - any-promise "^1.0.0" - object-assign "^4.0.1" - thenify-all "^1.0.0" +ms@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" + integrity sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg== -nan@2.10.0: - version "2.10.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.10.0.tgz#96d0cd610ebd58d4b4de9cc0c6828cda99c7548f" - -nan@^2.0.8, nan@^2.3.3, nan@^2.9.2: - version "2.11.0" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.11.0.tgz#574e360e4d954ab16966ec102c0c049fd961a099" - -nan@^2.2.1: - version "2.11.1" - resolved "https://registry.yarnpkg.com/nan/-/nan-2.11.1.tgz#90e22bccb8ca57ea4cd37cc83d3819b52eea6766" - -nano-json-stream-parser@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/nano-json-stream-parser/-/nano-json-stream-parser-0.1.2.tgz#0cc8f6d0e2b622b479c40d499c46d64b755c6f5f" +nan@^2.9.2: + version "2.12.1" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.12.1.tgz#7b1aa193e9aa86057e3c7bbd0ac448e770925552" + integrity sha512-JY7V6lRkStKcKTvHO5NVSQRv+RV+FIL5pvDoLiAtSL9pKlC5x9PKQcZDsq7m4FO4d57mkhC6Z+QhAh3Jdk5JFw== nanomatch@^1.2.9: version "1.2.13" resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" + integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== dependencies: arr-diff "^4.0.0" array-unique "^0.3.2" @@ -3337,28 +3075,28 @@ nanomatch@^1.2.9: to-regex "^3.0.1" needle@^2.2.1: - version "2.2.2" - resolved "https://registry.yarnpkg.com/needle/-/needle-2.2.2.tgz#1120ca4c41f2fcc6976fd28a8968afe239929418" + version "2.2.4" + resolved "https://registry.yarnpkg.com/needle/-/needle-2.2.4.tgz#51931bff82533b1928b7d1d69e01f1b00ffd2a4e" + integrity sha512-HyoqEb4wr/rsoaIDfTH2aVL9nWtQqba2/HvMv+++m8u0dz808MaagKILxtfeSN7QU7nvbQ79zk3vYOJp9zsNEA== dependencies: debug "^2.1.2" iconv-lite "^0.4.4" sax "^1.2.4" -negotiator@0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.1.tgz#2b327184e8992101177b28563fb5e7102acd0ca9" - neo-async@^2.5.0: - version "2.5.2" - resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.5.2.tgz#489105ce7bc54e709d736b195f82135048c50fcc" + version "2.6.0" + resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.0.tgz#b9d15e4d71c6762908654b5183ed38b753340835" + integrity sha512-MFh0d/Wa7vkKO3Y3LlacqAEeHK0mckVqzDieUKTT+KGxi+zIpeVsFxymkIiRpbpDziHc290Xr9A1O4Om7otoRA== nice-try@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366" + integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ== node-libs-browser@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.1.0.tgz#5f94263d404f6e44767d726901fff05478d600df" + version "2.2.0" + resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.0.tgz#c72f60d9d46de08a940dedbb25f3ffa2f9bbaa77" + integrity sha512-5MQunG/oyOaBdttrL40dA7bUfPORLRWMUJLQtMg7nluxUvk5XwnLdL9twQHFAjRx/y7mIMkLKT9++qPbbk6BZA== dependencies: assert "^1.1.1" browserify-zlib "^0.2.0" @@ -3367,7 +3105,7 @@ node-libs-browser@^2.0.0: constants-browserify "^1.0.0" crypto-browserify "^3.11.0" domain-browser "^1.1.1" - events "^1.0.0" + events "^3.0.0" https-browserify "^1.0.0" os-browserify "^0.3.0" path-browserify "0.0.0" @@ -3381,12 +3119,13 @@ node-libs-browser@^2.0.0: timers-browserify "^2.0.4" tty-browserify "0.0.0" url "^0.11.0" - util "^0.10.3" + util "^0.11.0" vm-browserify "0.0.4" node-pre-gyp@^0.10.0: version "0.10.3" resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.10.3.tgz#3070040716afdc778747b61b6887bf78880b80fc" + integrity sha512-d1xFs+C/IPS8Id0qPTZ4bUT8wWryfR/OzzAFxweG+uLN85oPzyo2Iw6bVlLQ/JOdgNonXLCoRyqDzDWq4iw72A== dependencies: detect-libc "^1.0.2" mkdirp "^0.5.1" @@ -3399,9 +3138,17 @@ node-pre-gyp@^0.10.0: semver "^5.3.0" tar "^4" +node-releases@^1.1.8: + version "1.1.9" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.9.tgz#70d0985ec4bf7de9f08fc481f5dae111889ca482" + integrity sha512-oic3GT4OtbWWKfRolz5Syw0Xus0KRFxeorLNj0s93ofX6PWyuzKjsiGxsCtWktBwwmTF6DdRRf2KreGqeOk5KA== + dependencies: + semver "^5.3.0" + nopt@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" + integrity sha1-0NRoWv1UFRk8jHUFYC0NF81kR00= dependencies: abbrev "1" osenv "^0.1.4" @@ -3409,16 +3156,24 @@ nopt@^4.0.1: normalize-path@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" + integrity sha1-GrKLVW4Zg2Oowab35vogE3/mrtk= dependencies: remove-trailing-separator "^1.0.1" +normalize-path@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + npm-bundled@^1.0.1: - version "1.0.5" - resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.5.tgz#3c1732b7ba936b3a10325aef616467c0ccbcc979" + version "1.0.6" + resolved "https://registry.yarnpkg.com/npm-bundled/-/npm-bundled-1.0.6.tgz#e7ba9aadcef962bb61248f91721cd932b3fe6bdd" + integrity sha512-8/JCaftHwbd//k6y2rEWp6k1wxVfpFzB6t1p825+cUb7Ym2XQfhwIC5KwhrvzZRJu+LtDE585zVaS32+CGtf0g== npm-packlist@^1.1.6: - version "1.1.11" - resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.1.11.tgz#84e8c683cbe7867d34b1d357d893ce29e28a02de" + version "1.4.1" + resolved "https://registry.yarnpkg.com/npm-packlist/-/npm-packlist-1.4.1.tgz#19064cdf988da80ea3cee45533879d90192bbfbc" + integrity sha512-+TcdO7HJJ8peiiYhvPxsEDhF3PJFGUGRcFsGve3vxvxdcpO2Z4Z7rkosRM0kWj6LfbK/P0gu3dzk5RU1ffvFcw== dependencies: ignore-walk "^3.0.1" npm-bundled "^1.0.1" @@ -3426,12 +3181,14 @@ npm-packlist@^1.1.6: npm-run-path@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-2.0.2.tgz#35a9232dfa35d7067b4cb2ddf2357b1871536c5f" + integrity sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8= dependencies: path-key "^2.0.0" npmlog@^4.0.2: version "4.1.2" resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.2.tgz#08a7f2a8bf734604779a9efa4ad5cc717abb954b" + integrity sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg== dependencies: are-we-there-yet "~1.1.2" console-control-strings "~1.1.0" @@ -3441,71 +3198,70 @@ npmlog@^4.0.2: number-is-nan@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" - -number-to-bn@1.7.0: - version "1.7.0" - resolved "https://registry.yarnpkg.com/number-to-bn/-/number-to-bn-1.7.0.tgz#bb3623592f7e5f9e0030b1977bd41a0c53fe1ea0" - dependencies: - bn.js "4.11.6" - strip-hex-prefix "1.0.0" + integrity sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0= oauth-sign@~0.8.2: version "0.8.2" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" + integrity sha1-Rqarfwrq2N6unsBWV4C31O/rnUM= -oauth-sign@~0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" - -object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: +object-assign@^4.0.1, object-assign@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= object-copy@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" + integrity sha1-fn2Fi3gb18mRpBupde04EnVOmYw= dependencies: copy-descriptor "^0.1.0" define-property "^0.2.5" kind-of "^3.0.3" +object-keys@^1.0.12: + version "1.1.0" + resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.0.tgz#11bd22348dd2e096a045ab06f6c85bcc340fa032" + integrity sha512-6OO5X1+2tYkNyNEx6TsCxEqFfRWaqx6EtMiSbGrw8Ob8v9Ne+Hl8rBAgLBZn5wjEz3s/s6U1WXFUFOcxxAwUpg== + object-visit@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" + integrity sha1-95xEk68MU3e1n+OdOV5BBC3QRbs= dependencies: isobject "^3.0.0" +object.getownpropertydescriptors@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz#8758c846f5b407adab0f236e0986f14b051caa16" + integrity sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY= + dependencies: + define-properties "^1.1.2" + es-abstract "^1.5.1" + object.pick@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" + integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c= dependencies: isobject "^3.0.1" -oboe@2.1.3: - version "2.1.3" - resolved "https://registry.yarnpkg.com/oboe/-/oboe-2.1.3.tgz#2b4865dbd46be81225713f4e9bfe4bcf4f680a4f" - dependencies: - http-https "^1.0.0" - -on-finished@~2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.3.0.tgz#20f1336481b083cd75337992a16971aa2d906947" - dependencies: - ee-first "1.1.1" - once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= dependencies: wrappy "1" onetime@^1.0.0: version "1.1.0" - resolved "http://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-1.1.0.tgz#a1f7838f8314c516f05ecefcbc4ccfe04b4ed789" + integrity sha1-ofeDj4MUxRbwXs78vEzP4EtO14k= ora@^0.2.3: version "0.2.3" - resolved "http://registry.npmjs.org/ora/-/ora-0.2.3.tgz#37527d220adcd53c39b73571d754156d5db657a4" + resolved "https://registry.yarnpkg.com/ora/-/ora-0.2.3.tgz#37527d220adcd53c39b73571d754156d5db657a4" + integrity sha1-N1J9Igrc1Tw5tzVx11QVbV22V6Q= dependencies: chalk "^1.1.1" cli-cursor "^1.0.2" @@ -3515,124 +3271,125 @@ ora@^0.2.3: os-browserify@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/os-browserify/-/os-browserify-0.3.0.tgz#854373c7f5c2315914fc9bfc6bd8238fdda1ec27" + integrity sha1-hUNzx/XCMVkU/Jv8a9gjj92h7Cc= os-homedir@^1.0.0, os-homedir@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" + integrity sha1-/7xJiDNuDoM94MFox+8VISGqf7M= -os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.1: +os-tmpdir@^1.0.0, os-tmpdir@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + integrity sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ= osenv@^0.1.4: version "0.1.5" resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.5.tgz#85cdfafaeb28e8677f416e287592b5f3f49ea410" + integrity sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g== dependencies: os-homedir "^1.0.0" os-tmpdir "^1.0.0" -p-cancelable@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.3.0.tgz#b9e123800bcebb7ac13a479be195b507b98d30fa" - p-finally@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" + integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4= -p-limit@^1.1.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.3.0.tgz#b86bd5f0c25690911c7590fcbfc2010d54b3ccb8" +p-limit@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.2.0.tgz#417c9941e6027a9abcba5092dd2904e255b5fbc2" + integrity sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ== dependencies: - p-try "^1.0.0" + p-try "^2.0.0" -p-locate@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" +p-locate@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-3.0.0.tgz#322d69a05c0264b25997d9f40cd8a891ab0064a4" + integrity sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ== dependencies: - p-limit "^1.1.0" + p-limit "^2.0.0" p-map@^1.1.1: version "1.2.0" resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.2.0.tgz#e4e94f311eabbc8633a1e79908165fca26241b6b" + integrity sha512-r6zKACMNhjPJMTl8KcFH4li//gkrXWfbD6feV8l6doRHlzljFWGJ2AP6iKaCJXyZmAUMOPtvbW7EXkbWO/pLEA== -p-timeout@^1.1.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-1.2.1.tgz#5eb3b353b7fce99f101a1038880bb054ebbea386" - dependencies: - p-finally "^1.0.0" - -p-try@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3" +p-try@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.0.0.tgz#85080bb87c64688fa47996fe8f7dfbe8211760b1" + integrity sha512-hMp0onDKIajHfIkdRk3P4CdCmErkYAxxDtP3Wx/4nZ3aGlau2VKh3mZpcuFkH27WQkL/3WBCPOktzA9ZOAnMQQ== pako@~1.0.5: - version "1.0.6" - resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.6.tgz#0101211baa70c4bca4a0f63f2206e97b7dfaf258" + version "1.0.10" + resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.10.tgz#4328badb5086a426aa90f541977d4955da5c9732" + integrity sha512-0DTvPVU3ed8+HNXOu5Bs+o//Mbdj9VNQMUOe9oKCwh8l0GNwpTDMKCWbRjgtD291AWnkAgkqA/LOnQS8AmS1tw== parallel-transform@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/parallel-transform/-/parallel-transform-1.1.0.tgz#d410f065b05da23081fcd10f28854c29bda33b06" + integrity sha1-1BDwZbBdojCB/NEPKIVMKb2jOwY= dependencies: cyclist "~0.2.2" inherits "^2.0.3" readable-stream "^2.1.5" parse-asn1@^5.0.0: - version "5.1.1" - resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.1.tgz#f6bf293818332bd0dab54efb16087724745e6ca8" + version "5.1.4" + resolved "https://registry.yarnpkg.com/parse-asn1/-/parse-asn1-5.1.4.tgz#37f6628f823fbdeb2273b4d540434a22f3ef1fcc" + integrity sha512-Qs5duJcuvNExRfFZ99HDD3z4mAi3r9Wl/FOjEOijlxwCZs7E7mW2vjTpgQ4J8LpTF8x5v+1Vn5UQFejmWT11aw== dependencies: asn1.js "^4.0.0" browserify-aes "^1.0.0" create-hash "^1.1.0" evp_bytestokey "^1.0.0" pbkdf2 "^3.0.3" - -parse-headers@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/parse-headers/-/parse-headers-2.0.1.tgz#6ae83a7aa25a9d9b700acc28698cd1f1ed7e9536" - dependencies: - for-each "^0.3.2" - trim "0.0.1" - -parseurl@~1.3.2: - version "1.3.2" - resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3" + safe-buffer "^5.1.1" pascalcase@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" + integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= path-browserify@0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-0.0.0.tgz#a0b870729aae214005b7d5032ec2cbbb0fb4451a" + integrity sha1-oLhwcpquIUAFt9UDLsLLuw+0RRo= path-dirname@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/path-dirname/-/path-dirname-1.0.2.tgz#cc33d24d525e099a5388c0336c6e32b9160609e0" + integrity sha1-zDPSTVJeCZpTiMAzbG4yuRYGCeA= path-exists@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" + integrity sha1-zg6+ql94yxiSXqfYENe1mwEP1RU= -path-is-absolute@^1.0.0, path-is-absolute@^1.0.1: +path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= path-is-inside@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" + integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM= path-key@^2.0.0, path-key@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40" + integrity sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A= -path-to-regexp@0.1.7: - version "0.1.7" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" +path-parse@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" + integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== pbkdf2@^3.0.3: - version "3.0.16" - resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.16.tgz#7404208ec6b01b62d85bf83853a8064f8d9c2a5c" + version "3.0.17" + resolved "https://registry.yarnpkg.com/pbkdf2/-/pbkdf2-3.0.17.tgz#976c206530617b14ebb32114239f7b09336e93a6" + integrity sha512-U/il5MsrZp7mGg3mSQfn742na2T+1/vHDCG5/iTI3X9MKUuYUZVLQhyRsg06mCgDBTd57TxzgZt7P+fYfjRLtA== dependencies: create-hash "^1.1.2" create-hmac "^1.1.4" @@ -3643,99 +3400,84 @@ pbkdf2@^3.0.3: pend@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/pend/-/pend-1.2.0.tgz#7a57eb550a6783f9115331fcf4663d5c8e007a50" + integrity sha1-elfrVQpng/kRUzH89GY9XI4AelA= performance-now@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" + integrity sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns= -pify@^2.2.0, pify@^2.3.0: +pify@^2.2.0: version "2.3.0" resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" + integrity sha1-7RQaasBDqEnqWISY59yosVMw6Qw= pify@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176" + integrity sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY= -pinkie-promise@^2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" +pkg-dir@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-3.0.0.tgz#2749020f239ed990881b1f71210d51eb6523bea3" + integrity sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw== dependencies: - pinkie "^2.0.0" - -pinkie@^2.0.0: - version "2.0.4" - resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" - -pkg-dir@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-2.0.0.tgz#f6d5d1109e19d63edf428e0bd57e12777615334b" - dependencies: - find-up "^2.1.0" + find-up "^3.0.0" posix-character-classes@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" + integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= -prepend-http@^1.0.1: - version "1.0.4" - resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc" - -private@^0.1.6, private@^0.1.7, private@^0.1.8: +private@^0.1.6: version "0.1.8" resolved "https://registry.yarnpkg.com/private/-/private-0.1.8.tgz#2381edb3689f7a53d653190060fcf822d2f368ff" + integrity sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg== process-nextick-args@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.0.tgz#a37d732f4271b4ab1ad070d35508e8290788ffaa" + integrity sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw== process@^0.11.10: version "0.11.10" resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182" - -process@~0.5.1: - version "0.5.2" - resolved "https://registry.yarnpkg.com/process/-/process-0.5.2.tgz#1638d8a8e34c2f440a91db95ab9aeb677fc185cf" - -progress@1.1.8: - version "1.1.8" - resolved "https://registry.yarnpkg.com/progress/-/progress-1.1.8.tgz#e260c78f6161cdd9b0e56cc3e0a85de17c7a57be" + integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI= promise-inflight@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3" - -proxy-addr@~2.0.3: - version "2.0.4" - resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.4.tgz#ecfc733bf22ff8c6f407fa275327b9ab67e48b93" - dependencies: - forwarded "~0.1.2" - ipaddr.js "1.8.0" + integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM= prr@~1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476" - -pseudomap@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3" - -psl@^1.1.24: - version "1.1.29" - resolved "https://registry.yarnpkg.com/psl/-/psl-1.1.29.tgz#60f580d360170bb722a797cc704411e6da850c67" + integrity sha1-0/wRS6BplaRexok/SEzrHXj19HY= public-encrypt@^4.0.0: - version "4.0.2" - resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.2.tgz#46eb9107206bf73489f8b85b69d91334c6610994" + version "4.0.3" + resolved "https://registry.yarnpkg.com/public-encrypt/-/public-encrypt-4.0.3.tgz#4fcc9d77a07e48ba7527e7cbe0de33d0701331e0" + integrity sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q== dependencies: bn.js "^4.1.0" browserify-rsa "^4.0.0" create-hash "^1.1.0" parse-asn1 "^5.0.0" randombytes "^2.0.1" + safe-buffer "^5.1.2" -pump@^2.0.0, pump@^2.0.1: +pump@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/pump/-/pump-2.0.1.tgz#12399add6e4cf7526d973cbc8b5ce2e2908b3909" + integrity sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA== + dependencies: + end-of-stream "^1.1.0" + once "^1.3.1" + +pump@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/pump/-/pump-3.0.0.tgz#b4a2116815bde2f4e1ea602354e8c75565107a64" + integrity sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww== dependencies: end-of-stream "^1.1.0" once "^1.3.1" @@ -3743,6 +3485,7 @@ pump@^2.0.0, pump@^2.0.1: pumpify@^1.3.3: version "1.5.1" resolved "https://registry.yarnpkg.com/pumpify/-/pumpify-1.5.1.tgz#36513be246ab27570b1a374a5ce278bfd74370ce" + integrity sha512-oClZI37HvuUJJxSKKrC17bZ9Cu0ZYhEAGPsPUy9KlMUmv9dKX2o77RUmq7f3XjIxbwyGwYzbzQ1L2Ks8sIradQ== dependencies: duplexify "^3.6.0" inherits "^2.0.3" @@ -3751,98 +3494,67 @@ pumpify@^1.3.3: punycode@1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d" - -punycode@2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.0.tgz#5f863edc89b96db09074bad7947bf09056ca4e7d" + integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0= punycode@^1.2.4, punycode@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" + integrity sha1-wNWmOycYgArY4esPpSachN1BhF4= punycode@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.1.tgz#b58b010ac40c22c5657616c8d2c2c02c7bf479ec" + integrity sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A== -qs@6.5.1: - version "6.5.1" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" - -qs@6.5.2, qs@~6.5.1, qs@~6.5.2: +qs@~6.5.1: version "6.5.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36" - -query-string@^5.0.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/query-string/-/query-string-5.1.1.tgz#a78c012b71c17e05f2e3fa2319dd330682efb3cb" - dependencies: - decode-uri-component "^0.2.0" - object-assign "^4.1.0" - strict-uri-encode "^1.0.0" + integrity sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA== querystring-es3@^0.2.0: version "0.2.1" resolved "https://registry.yarnpkg.com/querystring-es3/-/querystring-es3-0.2.1.tgz#9ec61f79049875707d69414596fd907a4d711e73" + integrity sha1-nsYfeQSYdXB9aUFFlv2Qek1xHnM= querystring@0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/querystring/-/querystring-0.2.0.tgz#b209849203bb25df820da756e747005878521620" + integrity sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA= ramda@0.24.1: version "0.24.1" resolved "https://registry.yarnpkg.com/ramda/-/ramda-0.24.1.tgz#c3b7755197f35b8dc3502228262c4c91ddb6b857" + integrity sha1-w7d1UZfzW43DUCIoJixMkd22uFc= randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5: - version "2.0.6" - resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.0.6.tgz#d302c522948588848a8d300c932b44c24231da80" + version "2.1.0" + resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" + integrity sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ== dependencies: safe-buffer "^5.1.0" randomfill@^1.0.3: version "1.0.4" resolved "https://registry.yarnpkg.com/randomfill/-/randomfill-1.0.4.tgz#c92196fc86ab42be983f1bf31778224931d61458" + integrity sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw== dependencies: randombytes "^2.0.5" safe-buffer "^5.1.0" -randomhex@0.1.5: - version "0.1.5" - resolved "https://registry.yarnpkg.com/randomhex/-/randomhex-0.1.5.tgz#baceef982329091400f2a2912c6cd02f1094f585" - -range-parser@~1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e" - -raw-body@2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.2.tgz#bcd60c77d3eb93cde0050295c3f379389bc88f89" - dependencies: - bytes "3.0.0" - http-errors "1.6.2" - iconv-lite "0.4.19" - unpipe "1.0.0" - -raw-body@2.3.3: - version "2.3.3" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.3.3.tgz#1b324ece6b5706e153855bc1148c65bb7f6ea0c3" - dependencies: - bytes "3.0.0" - http-errors "1.6.3" - iconv-lite "0.4.23" - unpipe "1.0.0" - rc@^1.2.7: version "1.2.8" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" + integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw== dependencies: deep-extend "^0.6.0" ini "~1.3.0" minimist "^1.2.0" strip-json-comments "~2.0.1" -"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.4, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.0, readable-stream@^2.3.3, readable-stream@^2.3.5, readable-stream@^2.3.6: +"readable-stream@1 || 2", readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.6, readable-stream@^2.1.5, readable-stream@^2.2.2, readable-stream@^2.3.3, readable-stream@^2.3.6, readable-stream@~2.3.6: version "2.3.6" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.6.tgz#b11c27d88b8ff1fbe070643cf94b0c79ae1b0aaf" + integrity sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw== dependencies: core-util-is "~1.0.0" inherits "~2.0.3" @@ -3852,83 +3564,109 @@ rc@^1.2.7: string_decoder "~1.1.1" util-deprecate "~1.0.1" -readdirp@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.1.0.tgz#4ed0ad060df3073300c48440373f72d1cc642d78" +readdirp@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.2.1.tgz#0e87622a3325aa33e892285caf8b4e846529a525" + integrity sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ== dependencies: - graceful-fs "^4.1.2" - minimatch "^3.0.2" + graceful-fs "^4.1.11" + micromatch "^3.1.10" readable-stream "^2.0.2" - set-immediate-shim "^1.0.1" -regenerate@^1.2.1: +regenerate-unicode-properties@^8.0.1: + version "8.0.1" + resolved "https://registry.yarnpkg.com/regenerate-unicode-properties/-/regenerate-unicode-properties-8.0.1.tgz#58a4a74e736380a7ab3c5f7e03f303a941b31289" + integrity sha512-HTjMafphaH5d5QDHuwW8Me6Hbc/GhXg8luNqTkPVwZ/oCZhnoifjWhGYsu2BzepMELTlbnoVcXvV0f+2uDDvoQ== + dependencies: + regenerate "^1.4.0" + +regenerate@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11" + integrity sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg== regenerator-runtime@^0.11.0: version "0.11.1" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9" + integrity sha512-MguG95oij0fC3QV3URf4V2SDYGJhJnJGqvIIgdECeODCT98wSWDAJ94SSuVpYQUoTcGUIL6L4yNB7j1DFFHSBg== -regenerator-transform@^0.10.0: - version "0.10.1" - resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.10.1.tgz#1e4996837231da8b7f3cf4114d71b5691a0680dd" +regenerator-transform@^0.13.4: + version "0.13.4" + resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.13.4.tgz#18f6763cf1382c69c36df76c6ce122cc694284fb" + integrity sha512-T0QMBjK3J0MtxjPmdIMXm72Wvj2Abb0Bd4HADdfijwMdoIsyQZ6fWC7kDFhk2YinBBEMZDL7Y7wh0J1sGx3S4A== dependencies: - babel-runtime "^6.18.0" - babel-types "^6.19.0" private "^0.1.6" regex-not@^1.0.0, regex-not@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" + integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== dependencies: extend-shallow "^3.0.2" safe-regex "^1.1.0" -regexpu-core@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-2.0.0.tgz#49d038837b8dcf8bfa5b9a42139938e6ea2ae240" - dependencies: - regenerate "^1.2.1" - regjsgen "^0.2.0" - regjsparser "^0.1.4" - -regjsgen@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.2.0.tgz#6c016adeac554f75823fe37ac05b92d5a4edb1f7" - -regjsparser@^0.1.4: +regexp-tree@^0.1.0: version "0.1.5" - resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.1.5.tgz#7ee8f84dc6fa792d3fd0ae228d24bd949ead205c" + resolved "https://registry.yarnpkg.com/regexp-tree/-/regexp-tree-0.1.5.tgz#7cd71fca17198d04b4176efd79713f2998009397" + integrity sha512-nUmxvfJyAODw+0B13hj8CFVAxhe7fDEAgJgaotBu3nnR+IgGgZq59YedJP5VYTlkEfqjuK6TuRpnymKdatLZfQ== + +regexpu-core@^4.1.3, regexpu-core@^4.2.0: + version "4.5.3" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.5.3.tgz#72f572e03bb8b9f4f4d895a0ccc57e707f4af2e4" + integrity sha512-LON8666bTAlViVEPXMv65ZqiaR3rMNLz36PIaQ7D+er5snu93k0peR7FSvO0QteYbZ3GOkvfHKbGr/B1xDu9FA== + dependencies: + regenerate "^1.4.0" + regenerate-unicode-properties "^8.0.1" + regjsgen "^0.5.0" + regjsparser "^0.6.0" + unicode-match-property-ecmascript "^1.0.4" + unicode-match-property-value-ecmascript "^1.1.0" + +regjsgen@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.0.tgz#a7634dc08f89209c2049adda3525711fb97265dd" + integrity sha512-RnIrLhrXCX5ow/E5/Mh2O4e/oa1/jW0eaBKTSy3LaCj+M3Bqvm97GWDp2yUtzIs4LEn65zR2yiYGFqb2ApnzDA== + +regjsparser@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.6.0.tgz#f1e6ae8b7da2bae96c99399b868cd6c933a2ba9c" + integrity sha512-RQ7YyokLiQBomUJuUG8iGVvkgOLxwyZM8k6d3q5SAXpg4r5TZJZigKFvC6PpD+qQ98bCDC5YelPeA3EucDoNeQ== dependencies: jsesc "~0.5.0" remove-trailing-separator@^1.0.1: version "1.1.0" resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz#c24bce2a283adad5bc3f58e0d48249b92379d8ef" + integrity sha1-wkvOKig62tW8P1jg1IJJuSN52O8= repeat-element@^1.1.2: version "1.1.3" resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.3.tgz#782e0d825c0c5a3bb39731f84efee6b742e6b1ce" + integrity sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g== repeat-string@^1.6.1: version "1.6.1" resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" + integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= repeating@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" + integrity sha1-UhTFOpJtNVJwdSf7q0FdvAjQbdo= dependencies: is-finite "^1.0.0" request-progress@0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/request-progress/-/request-progress-0.3.1.tgz#0721c105d8a96ac6b2ce8b2c89ae2d5ecfcf6b3a" + integrity sha1-ByHBBdipasayzossia4tXs/Pazo= dependencies: throttleit "~0.0.2" request@2.87.0: version "2.87.0" resolved "https://registry.yarnpkg.com/request/-/request-2.87.0.tgz#32f00235cd08d482b4d0d68db93a829c0ed5756e" + integrity sha512-fcogkm7Az5bsS6Sl0sibkbhcKsnyon/jV1kF3ajGmF0c8HrttdKTPRT9hieOaQHA5HEq6r8OyWOo/o781C1tNw== dependencies: aws-sign2 "~0.7.0" aws4 "^1.6.0" @@ -3951,38 +3689,22 @@ request@2.87.0: tunnel-agent "^0.6.0" uuid "^3.1.0" -request@^2.79.0: - version "2.88.0" - resolved "https://registry.yarnpkg.com/request/-/request-2.88.0.tgz#9c2fca4f7d35b592efe57c7f0a55e81052124fef" - dependencies: - aws-sign2 "~0.7.0" - aws4 "^1.8.0" - caseless "~0.12.0" - combined-stream "~1.0.6" - extend "~3.0.2" - forever-agent "~0.6.1" - form-data "~2.3.2" - har-validator "~5.1.0" - http-signature "~1.2.0" - is-typedarray "~1.0.0" - isstream "~0.1.2" - json-stringify-safe "~5.0.1" - mime-types "~2.1.19" - oauth-sign "~0.9.0" - performance-now "^2.1.0" - qs "~6.5.2" - safe-buffer "^5.1.2" - tough-cookie "~2.4.3" - tunnel-agent "^0.6.0" - uuid "^3.3.2" - resolve-url@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" + integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= + +resolve@^1.3.2: + version "1.10.0" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.10.0.tgz#3bdaaeaf45cc07f375656dfd2e54ed0810b101ba" + integrity sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg== + dependencies: + path-parse "^1.0.6" restore-cursor@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-1.0.1.tgz#34661f46886327fed2991479152252df92daa541" + integrity sha1-NGYfRohjJ/7SmRR5FSJS35LapUE= dependencies: exit-hook "^1.0.0" onetime "^1.0.0" @@ -3990,165 +3712,87 @@ restore-cursor@^1.0.1: ret@~0.1.10: version "0.1.15" resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" + integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== -rimraf@2, rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2: - version "2.6.2" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.2.tgz#2ed8150d24a16ea8651e6d6ef0f47c4158ce7a36" +rimraf@^2.5.4, rimraf@^2.6.1, rimraf@^2.6.2: + version "2.6.3" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.3.tgz#b2d104fe0d8fb27cf9e0a1cda8262dd3833c6cab" + integrity sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA== dependencies: - glob "^7.0.5" + glob "^7.1.3" ripemd160@^2.0.0, ripemd160@^2.0.1: version "2.0.2" resolved "https://registry.yarnpkg.com/ripemd160/-/ripemd160-2.0.2.tgz#a1c1a6f624751577ba5d07914cbc92850585890c" + integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA== dependencies: hash-base "^3.0.0" inherits "^2.0.1" -rlp@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/rlp/-/rlp-2.1.0.tgz#e4f9886d5a982174f314543831e36e1a658460f9" - dependencies: - safe-buffer "^5.1.1" - run-queue@^1.0.0, run-queue@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/run-queue/-/run-queue-1.0.3.tgz#e848396f057d223f24386924618e25694161ec47" + integrity sha1-6Eg5bwV9Ij8kOGkkYY4laUFh7Ec= dependencies: aproba "^1.1.1" rxjs@^5.0.0-beta.11: version "5.5.12" resolved "https://registry.yarnpkg.com/rxjs/-/rxjs-5.5.12.tgz#6fa61b8a77c3d793dbaf270bee2f43f652d741cc" + integrity sha512-xx2itnL5sBbqeeiVgNPVuQQ1nC8Jp2WfNJhXWHmElW9YmrpS9UVnNzhP3EH3HFqexO5Tlp8GhYY+WEcqcVMvGw== dependencies: symbol-observable "1.0.1" -safe-buffer@5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" - safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" + integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== safe-regex@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" + integrity sha1-QKNmnzsHfR6UPURinhV91IAjvy4= dependencies: ret "~0.1.10" "safer-buffer@>= 2.1.2 < 3", safer-buffer@^2.0.2, safer-buffer@^2.1.0, safer-buffer@~2.1.0: version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" + integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg== sax@^1.2.4: version "1.2.4" resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" + integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== -schema-utils@^0.4.4, schema-utils@^0.4.5: - version "0.4.7" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-0.4.7.tgz#ba74f597d2be2ea880131746ee17d0a093c68187" +schema-utils@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-1.0.0.tgz#0b79a93204d7b600d4b2850d1f66c2a34951c770" + integrity sha512-i27Mic4KovM/lnGsy8whRCHhc7VicJajAjTrYg11K9zfZXnYIt4k5F+kZkwjnrhKzLic/HLU4j11mjsz2G/75g== dependencies: ajv "^6.1.0" + ajv-errors "^1.0.0" ajv-keywords "^3.1.0" -scrypt-js@2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/scrypt-js/-/scrypt-js-2.0.3.tgz#bb0040be03043da9a012a2cea9fc9f852cfc87d4" - -scrypt.js@0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/scrypt.js/-/scrypt.js-0.2.0.tgz#af8d1465b71e9990110bedfc593b9479e03a8ada" - dependencies: - scrypt "^6.0.2" - scryptsy "^1.2.1" - -scrypt@^6.0.2: - version "6.0.3" - resolved "https://registry.yarnpkg.com/scrypt/-/scrypt-6.0.3.tgz#04e014a5682b53fa50c2d5cce167d719c06d870d" - dependencies: - nan "^2.0.8" - -scryptsy@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/scryptsy/-/scryptsy-1.2.1.tgz#a3225fa4b2524f802700761e2855bdf3b2d92163" - dependencies: - pbkdf2 "^3.0.3" - -secp256k1@^3.0.1: - version "3.5.2" - resolved "https://registry.yarnpkg.com/secp256k1/-/secp256k1-3.5.2.tgz#f95f952057310722184fe9c914e6b71281f2f2ae" - dependencies: - bindings "^1.2.1" - bip66 "^1.1.3" - bn.js "^4.11.3" - create-hash "^1.1.2" - drbg.js "^1.0.1" - elliptic "^6.2.3" - nan "^2.2.1" - safe-buffer "^5.1.0" - -seek-bzip@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/seek-bzip/-/seek-bzip-1.0.5.tgz#cfe917cb3d274bcffac792758af53173eb1fabdc" - dependencies: - commander "~2.8.1" - -semver@^5.0.1, semver@^5.3.0, semver@^5.5.0: - version "5.5.1" - resolved "https://registry.yarnpkg.com/semver/-/semver-5.5.1.tgz#7dfdd8814bdb7cabc7be0fb1d734cfb66c940477" - -send@0.16.2: - version "0.16.2" - resolved "https://registry.yarnpkg.com/send/-/send-0.16.2.tgz#6ecca1e0f8c156d141597559848df64730a6bbc1" - dependencies: - debug "2.6.9" - depd "~1.1.2" - destroy "~1.0.4" - encodeurl "~1.0.2" - escape-html "~1.0.3" - etag "~1.8.1" - fresh "0.5.2" - http-errors "~1.6.2" - mime "1.4.1" - ms "2.0.0" - on-finished "~2.3.0" - range-parser "~1.2.0" - statuses "~1.4.0" +semver@^5.0.1, semver@^5.3.0, semver@^5.4.1, semver@^5.5.0: + version "5.6.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.6.0.tgz#7e74256fbaa49c75aa7c7a205cc22799cac80004" + integrity sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg== serialize-javascript@^1.4.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.5.0.tgz#1aa336162c88a890ddad5384baebc93a655161fe" - -serve-static@1.13.2: - version "1.13.2" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.13.2.tgz#095e8472fd5b46237db50ce486a43f4b86c6cec1" - dependencies: - encodeurl "~1.0.2" - escape-html "~1.0.3" - parseurl "~1.3.2" - send "0.16.2" - -servify@^0.1.12: - version "0.1.12" - resolved "https://registry.yarnpkg.com/servify/-/servify-0.1.12.tgz#142ab7bee1f1d033b66d0707086085b17c06db95" - dependencies: - body-parser "^1.16.0" - cors "^2.8.1" - express "^4.14.0" - request "^2.79.0" - xhr "^2.3.3" + version "1.6.1" + resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-1.6.1.tgz#4d1f697ec49429a847ca6f442a2a755126c4d879" + integrity sha512-A5MOagrPFga4YaKQSWHryl7AXvbQkEqpw4NNYMTNYUNV51bA8ABHgYFpqKx+YFFrw59xMV1qGH1R4AgoNIVgCw== set-blocking@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" - -set-immediate-shim@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" + integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= set-value@^0.4.3: version "0.4.3" resolved "https://registry.yarnpkg.com/set-value/-/set-value-0.4.3.tgz#7db08f9d3d22dc7f78e53af3c3bf4666ecdfccf1" + integrity sha1-fbCPnT0i3H945Trzw79GZuzfzPE= dependencies: extend-shallow "^2.0.1" is-extendable "^0.1.1" @@ -4158,78 +3802,52 @@ set-value@^0.4.3: set-value@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.0.tgz#71ae4a88f0feefbbf52d1ea604f3fb315ebb6274" + integrity sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg== dependencies: extend-shallow "^2.0.1" is-extendable "^0.1.1" is-plain-object "^2.0.3" split-string "^3.0.1" -setimmediate@1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.4.tgz#20e81de622d4a02588ce0c8da8973cbcf1d3138f" - -setimmediate@^1.0.4, setimmediate@^1.0.5: +setimmediate@^1.0.4: version "1.0.5" resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" - -setprototypeof@1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.0.3.tgz#66567e37043eeb4f04d91bd658c0cbefb55b8e04" - -setprototypeof@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.0.tgz#d0bd85536887b6fe7c0d818cb962d9d91c54e656" + integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= sha.js@^2.4.0, sha.js@^2.4.8: version "2.4.11" resolved "https://registry.yarnpkg.com/sha.js/-/sha.js-2.4.11.tgz#37a5cf0b81ecbc6943de109ba2960d1b26584ae7" + integrity sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ== dependencies: inherits "^2.0.1" safe-buffer "^5.0.1" -sha3@^1.1.0: - version "1.2.2" - resolved "https://registry.yarnpkg.com/sha3/-/sha3-1.2.2.tgz#a66c5098de4c25bc88336ec8b4817d005bca7ba9" - dependencies: - nan "2.10.0" - shebang-command@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea" + integrity sha1-RKrGW2lbAzmJaMOfNj/uXer98eo= dependencies: shebang-regex "^1.0.0" shebang-regex@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3" + integrity sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM= signal-exit@^3.0.0: version "3.0.2" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" - -simple-concat@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/simple-concat/-/simple-concat-1.0.0.tgz#7344cbb8b6e26fb27d66b2fc86f9f6d5997521c6" - -simple-get@^2.7.0: - version "2.8.1" - resolved "https://registry.yarnpkg.com/simple-get/-/simple-get-2.8.1.tgz#0e22e91d4575d87620620bc91308d57a77f44b5d" - dependencies: - decompress-response "^3.3.0" - once "^1.3.1" - simple-concat "^1.0.0" - -slash@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" + integrity sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0= slice-ansi@0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35" + integrity sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU= snapdragon-node@^2.0.1: version "2.1.1" resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" + integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== dependencies: define-property "^1.0.0" isobject "^3.0.0" @@ -4238,12 +3856,14 @@ snapdragon-node@^2.0.1: snapdragon-util@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" + integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== dependencies: kind-of "^3.2.0" snapdragon@^0.8.1: version "0.8.2" resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" + integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== dependencies: base "^0.11.1" debug "^2.2.0" @@ -4255,12 +3875,14 @@ snapdragon@^0.8.1: use "^3.1.0" source-list-map@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.0.tgz#aaa47403f7b245a92fbc97ea08f250d6087ed085" + version "2.0.1" + resolved "https://registry.yarnpkg.com/source-list-map/-/source-list-map-2.0.1.tgz#3993bd873bfc48479cca9ea3a547835c7c154b34" + integrity sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw== source-map-resolve@^0.5.0: version "0.5.2" resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.2.tgz#72e2cc34095543e43b2c62b2c4c10d4a9054f259" + integrity sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA== dependencies: atob "^2.1.1" decode-uri-component "^0.2.0" @@ -4268,69 +3890,70 @@ source-map-resolve@^0.5.0: source-map-url "^0.4.0" urix "^0.1.0" -source-map-support@^0.4.15: - version "0.4.18" - resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.18.tgz#0286a6de8be42641338594e97ccea75f0a2c585f" +source-map-support@~0.5.9: + version "0.5.10" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.10.tgz#2214080bc9d51832511ee2bab96e3c2f9353120c" + integrity sha512-YfQ3tQFTK/yzlGJuX8pTwa4tifQj4QS2Mj7UegOu8jAz59MqIiMGPXxQhVQiIMNzayuUSF/jEuVnfFF5JqybmQ== dependencies: - source-map "^0.5.6" + buffer-from "^1.0.0" + source-map "^0.6.0" source-map-url@^0.4.0: version "0.4.0" resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.0.tgz#3e935d7ddd73631b97659956d55128e87b5084a3" + integrity sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM= -source-map@^0.5.6, source-map@^0.5.7: +source-map@^0.5.0, source-map@^0.5.6: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" + integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= -source-map@^0.6.1, source-map@~0.6.1: +source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1: version "0.6.1" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== split-string@^3.0.1, split-string@^3.0.2: version "3.1.0" resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" + integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== dependencies: extend-shallow "^3.0.0" sshpk@^1.7.0: - version "1.14.2" - resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.14.2.tgz#c6fc61648a3d9c4e764fd3fcdf4ea105e492ba98" + version "1.16.1" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.16.1.tgz#fb661c0bef29b39db40769ee39fa70093d6f6877" + integrity sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg== dependencies: asn1 "~0.2.3" assert-plus "^1.0.0" - dashdash "^1.12.0" - getpass "^0.1.1" - safer-buffer "^2.0.2" - optionalDependencies: bcrypt-pbkdf "^1.0.0" + dashdash "^1.12.0" ecc-jsbn "~0.1.1" + getpass "^0.1.1" jsbn "~0.1.0" + safer-buffer "^2.0.2" tweetnacl "~0.14.0" -ssri@^5.2.4: - version "5.3.0" - resolved "https://registry.yarnpkg.com/ssri/-/ssri-5.3.0.tgz#ba3872c9c6d33a0704a7d71ff045e5ec48999d06" +ssri@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/ssri/-/ssri-6.0.1.tgz#2a3c41b28dd45b62b63676ecb74001265ae9edd8" + integrity sha512-3Wge10hNcT1Kur4PDFwEieXSCMCJs/7WvSACcrMYrNp+b8kDL1/0wJch5Ni2WrtwEa2IO8OsVfeKIciKCDx/QA== dependencies: - safe-buffer "^5.1.1" + figgy-pudding "^3.5.1" static-extend@^0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" + integrity sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY= dependencies: define-property "^0.2.5" object-copy "^0.1.0" -"statuses@>= 1.3.1 < 2", "statuses@>= 1.4.0 < 2": - version "1.5.0" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" - -statuses@~1.4.0: - version "1.4.0" - resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.4.0.tgz#bb73d446da2796106efcc1b601a253d6c46bd087" - stream-browserify@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.1.tgz#66266ee5f9bdb9940a4e4514cafb43bb71e5c9db" + version "2.0.2" + resolved "https://registry.yarnpkg.com/stream-browserify/-/stream-browserify-2.0.2.tgz#87521d38a44aa7ee91ce1cd2a47df0cb49dd660b" + integrity sha512-nX6hmklHs/gr2FuxYDltq8fJA1GDlxKQCz8O/IM4atRqBH8OORmBNgfvW5gG10GT/qQ9u0CzIvr2X5Pkt6ntqg== dependencies: inherits "~2.0.1" readable-stream "^2.0.2" @@ -4338,6 +3961,7 @@ stream-browserify@^2.0.1: stream-each@^1.1.0: version "1.2.3" resolved "https://registry.yarnpkg.com/stream-each/-/stream-each-1.2.3.tgz#ebe27a0c389b04fbcc233642952e10731afa9bae" + integrity sha512-vlMC2f8I2u/bZGqkdfLQW/13Zihpej/7PmSiMQsbYddxuTsJp8vRe2x2FvVExZg7FaOds43ROAuFJwPR4MTZLw== dependencies: end-of-stream "^1.1.0" stream-shift "^1.0.0" @@ -4345,6 +3969,7 @@ stream-each@^1.1.0: stream-http@^2.7.2: version "2.8.3" resolved "https://registry.yarnpkg.com/stream-http/-/stream-http-2.8.3.tgz#b2d242469288a5a27ec4fe8933acf623de6514fc" + integrity sha512-+TSkfINHDo4J+ZobQLWiMouQYB+UVYFttRA94FpEzzJ7ZdqcL4uUUQ7WkdkI4DSozGmgBUE/a47L+38PenXhUw== dependencies: builtin-status-codes "^3.0.0" inherits "^2.0.1" @@ -4355,18 +3980,17 @@ stream-http@^2.7.2: stream-shift@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/stream-shift/-/stream-shift-1.0.0.tgz#d5c752825e5367e786f78e18e445ea223a155952" + integrity sha1-1cdSgl5TZ+eG944Y5EXqIjoVWVI= stream-to-observable@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/stream-to-observable/-/stream-to-observable-0.1.0.tgz#45bf1d9f2d7dc09bed81f1c307c430e68b84cffe" - -strict-uri-encode@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" + integrity sha1-Rb8dny19wJvtgfHDB8Qw5ouEz/4= string-width@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" + integrity sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M= dependencies: code-point-at "^1.0.0" is-fullwidth-code-point "^1.0.0" @@ -4375,196 +3999,162 @@ string-width@^1.0.1: "string-width@^1.0.2 || 2": version "2.1.1" resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e" + integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw== dependencies: is-fullwidth-code-point "^2.0.0" strip-ansi "^4.0.0" -string_decoder@^1.0.0, string_decoder@~1.1.1: +string_decoder@^1.0.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.2.0.tgz#fe86e738b19544afe70469243b2a1ee9240eae8d" + integrity sha512-6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w== + dependencies: + safe-buffer "~5.1.0" + +string_decoder@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" + integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== dependencies: safe-buffer "~5.1.0" strip-ansi@^3.0.0, strip-ansi@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + integrity sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8= dependencies: ansi-regex "^2.0.0" strip-ansi@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f" + integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8= dependencies: ansi-regex "^3.0.0" -strip-dirs@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/strip-dirs/-/strip-dirs-2.1.0.tgz#4987736264fc344cf20f6c34aca9d13d1d4ed6c5" - dependencies: - is-natural-number "^4.0.1" - strip-eof@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/strip-eof/-/strip-eof-1.0.0.tgz#bb43ff5598a6eb05d89b59fcd129c983313606bf" - -strip-hex-prefix@1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/strip-hex-prefix/-/strip-hex-prefix-1.0.0.tgz#0c5f155fef1151373377de9dbb588da05500e36f" - dependencies: - is-hex-prefixed "1.0.0" + integrity sha1-u0P/VZim6wXYm1n80SnJgzE2Br8= strip-json-comments@~2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo= supports-color@5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.1.0.tgz#058a021d1b619f7ddf3980d712ea3590ce7de3d5" + integrity sha512-Ry0AwkoKjDpVKK4sV4h6o3UJmNRbjYm2uXhwfj3J56lMVdvnUNqzQVRztOOMGQ++w1K/TjNDFvpJk0F/LoeBCQ== dependencies: has-flag "^2.0.0" supports-color@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" + integrity sha1-U10EXOa2Nj+kARcIRimZXp3zJMc= supports-color@^5.3.0: version "5.5.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f" + integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow== dependencies: has-flag "^3.0.0" -swarm-js@0.1.37: - version "0.1.37" - resolved "https://registry.yarnpkg.com/swarm-js/-/swarm-js-0.1.37.tgz#27d485317a340bbeec40292af783cc10acfa4663" - dependencies: - bluebird "^3.5.0" - buffer "^5.0.5" - decompress "^4.0.0" - eth-lib "^0.1.26" - fs-extra "^2.1.2" - fs-promise "^2.0.0" - got "^7.1.0" - mime-types "^2.1.16" - mkdirp-promise "^5.0.1" - mock-fs "^4.1.0" - setimmediate "^1.0.5" - tar.gz "^1.0.5" - xhr-request-promise "^0.1.2" - symbol-observable@1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-1.0.1.tgz#8340fc4702c3122df5d22288f88283f513d3fdd4" + integrity sha1-g0D8RwLDEi310iKI+IKD9RPT/dQ= -tapable@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.0.0.tgz#cbb639d9002eed9c6b5975eb20598d7936f1f9f2" - -tar-stream@^1.5.2: - version "1.6.1" - resolved "https://registry.yarnpkg.com/tar-stream/-/tar-stream-1.6.1.tgz#f84ef1696269d6223ca48f6e1eeede3f7e81f395" - dependencies: - bl "^1.0.0" - buffer-alloc "^1.1.0" - end-of-stream "^1.0.0" - fs-constants "^1.0.0" - readable-stream "^2.3.0" - to-buffer "^1.1.0" - xtend "^4.0.0" - -tar.gz@^1.0.5: - version "1.0.7" - resolved "https://registry.yarnpkg.com/tar.gz/-/tar.gz-1.0.7.tgz#577ef2c595faaa73452ef0415fed41113212257b" - dependencies: - bluebird "^2.9.34" - commander "^2.8.1" - fstream "^1.0.8" - mout "^0.11.0" - tar "^2.1.1" - -tar@^2.1.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1" - dependencies: - block-stream "*" - fstream "^1.0.2" - inherits "2" +tapable@^1.0.0, tapable@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/tapable/-/tapable-1.1.1.tgz#4d297923c5a72a42360de2ab52dadfaaec00018e" + integrity sha512-9I2ydhj8Z9veORCw5PRm4u9uebCn0mcCa6scWoNcbZ6dAtoo2618u9UUzxgmsCOreJpqDDuv61LvwofW7hLcBA== tar@^4: - version "4.4.6" - resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.6.tgz#63110f09c00b4e60ac8bcfe1bf3c8660235fbc9b" + version "4.4.8" + resolved "https://registry.yarnpkg.com/tar/-/tar-4.4.8.tgz#b19eec3fde2a96e64666df9fdb40c5ca1bc3747d" + integrity sha512-LzHF64s5chPQQS0IYBn9IN5h3i98c12bo4NCO7e0sGM2llXQ3p2FGC5sdENN4cTW48O915Sh+x+EXx7XW96xYQ== dependencies: - chownr "^1.0.1" + chownr "^1.1.1" fs-minipass "^1.2.5" - minipass "^2.3.3" - minizlib "^1.1.0" + minipass "^2.3.4" + minizlib "^1.1.1" mkdirp "^0.5.0" safe-buffer "^5.1.2" yallist "^3.0.2" -thenify-all@^1.0.0, thenify-all@^1.6.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" +terser-webpack-plugin@^1.1.0: + version "1.2.3" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.2.3.tgz#3f98bc902fac3e5d0de730869f50668561262ec8" + integrity sha512-GOK7q85oAb/5kE12fMuLdn2btOS9OBZn4VsecpHDywoUC/jLhSAKOiYo0ezx7ss2EXPMzyEWFoE0s1WLE+4+oA== dependencies: - thenify ">= 3.1.0 < 4" + cacache "^11.0.2" + find-cache-dir "^2.0.0" + schema-utils "^1.0.0" + serialize-javascript "^1.4.0" + source-map "^0.6.1" + terser "^3.16.1" + webpack-sources "^1.1.0" + worker-farm "^1.5.2" -"thenify@>= 3.1.0 < 4": - version "3.3.0" - resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.0.tgz#e69e38a1babe969b0108207978b9f62b88604839" +terser@^3.16.1: + version "3.16.1" + resolved "https://registry.yarnpkg.com/terser/-/terser-3.16.1.tgz#5b0dd4fa1ffd0b0b43c2493b2c364fd179160493" + integrity sha512-JDJjgleBROeek2iBcSNzOHLKsB/MdDf+E/BOAJ0Tk9r7p9/fVobfv7LMJ/g/k3v9SXdmjZnIlFd5nfn/Rt0Xow== dependencies: - any-promise "^1.0.0" + commander "~2.17.1" + source-map "~0.6.1" + source-map-support "~0.5.9" throttleit@~0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/throttleit/-/throttleit-0.0.2.tgz#cfedf88e60c00dd9697b61fdd2a8343a9b680eaf" + integrity sha1-z+34jmDADdlpe2H90qg0OptoDq8= through2@^2.0.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.3.tgz#0004569b37c7c74ba39c43f3ced78d1ad94140be" + version "2.0.5" + resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" + integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== dependencies: - readable-stream "^2.1.5" + readable-stream "~2.3.6" xtend "~4.0.1" -through@^2.3.6: - version "2.3.8" - resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" - -timed-out@^4.0.0, timed-out@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f" - timers-browserify@^2.0.4: version "2.0.10" resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.10.tgz#1d28e3d2aadf1d5a5996c4e9f95601cd053480ae" + integrity sha512-YvC1SV1XdOUaL6gx5CoGroT3Gu49pK9+TZ38ErPldOWW4j49GI1HKs9DV+KGq/w6y+LZ72W1c8cKz2vzY+qpzg== dependencies: setimmediate "^1.0.4" tmp@0.0.31: version "0.0.31" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.31.tgz#8f38ab9438e17315e5dbd8b3657e8bfb277ae4a7" + integrity sha1-jzirlDjhcxXl29izZX6L+yd65Kc= dependencies: os-tmpdir "~1.0.1" to-arraybuffer@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" + integrity sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M= -to-buffer@^1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/to-buffer/-/to-buffer-1.1.1.tgz#493bd48f62d7c43fcded313a03dcadb2e1213a80" - -to-fast-properties@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47" +to-fast-properties@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" + integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= to-object-path@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" + integrity sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68= dependencies: kind-of "^3.0.2" to-regex-range@^2.1.0: version "2.1.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" + integrity sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg= dependencies: is-number "^3.0.0" repeat-string "^1.6.1" @@ -4572,6 +4162,7 @@ to-regex-range@^2.1.0: to-regex@^3.0.1, to-regex@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" + integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== dependencies: define-property "^2.0.2" extend-shallow "^3.0.2" @@ -4581,27 +4172,19 @@ to-regex@^3.0.1, to-regex@^3.0.2: tough-cookie@~2.3.3: version "2.3.4" resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.4.tgz#ec60cee38ac675063ffc97a5c18970578ee83655" + integrity sha512-TZ6TTfI5NtZnuyy/Kecv+CnoROnyXn2DN97LontgQpCwsX2XyLYCC0ENhYkehSOwAp8rTQKc/NUIF7BkQ5rKLA== dependencies: punycode "^1.4.1" -tough-cookie@~2.4.3: - version "2.4.3" - resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.4.3.tgz#53f36da3f47783b0925afa06ff9f3b165280f781" - dependencies: - psl "^1.1.24" - punycode "^1.4.1" - trim-right@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" - -trim@0.0.1: - version "0.0.1" - resolved "https://registry.yarnpkg.com/trim/-/trim-0.0.1.tgz#5858547f6b290757ee95cccc666fb50084c460dd" + integrity sha1-yy4SAwZ+DI3h9hQJS5/kVwTqYAM= ts-loader@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-5.0.0.tgz#0715ee31ed229fcdc8df73e87ccd80e6a46cae04" + version "5.3.3" + resolved "https://registry.yarnpkg.com/ts-loader/-/ts-loader-5.3.3.tgz#8b4af042e773132d86b3c99ef0acf3b4d325f473" + integrity sha512-KwF1SplmOJepnoZ4eRIloH/zXL195F51skt7reEsS6jvDqzgc/YSbz9b8E07GxIUwLXdcD4ssrJu6v8CwaTafA== dependencies: chalk "^2.3.0" enhanced-resolve "^4.0.0" @@ -4612,152 +4195,116 @@ ts-loader@^5.0.0: tslib@^1.9.0: version "1.9.3" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286" + integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ== tty-browserify@0.0.0: version "0.0.0" resolved "https://registry.yarnpkg.com/tty-browserify/-/tty-browserify-0.0.0.tgz#a157ba402da24e9bf957f9aa69d524eed42901a6" + integrity sha1-oVe6QC2iTpv5V/mqadUk7tQpAaY= tunnel-agent@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + integrity sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0= dependencies: safe-buffer "^5.0.1" -tweetnacl-util@^0.15.0: - version "0.15.0" - resolved "https://registry.yarnpkg.com/tweetnacl-util/-/tweetnacl-util-0.15.0.tgz#4576c1cee5e2d63d207fee52f1ba02819480bc75" - tweetnacl@^0.14.3, tweetnacl@~0.14.0: version "0.14.5" resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" - -tweetnacl@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-1.0.0.tgz#713d8b818da42068740bf68386d0479e66fc8a7b" - -type-is@~1.6.15, type-is@~1.6.16: - version "1.6.16" - resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.16.tgz#f89ce341541c672b25ee7ae3c73dee3b2be50194" - dependencies: - media-typer "0.3.0" - mime-types "~2.1.18" - -typedarray-to-buffer@^3.1.2: - version "3.1.5" - resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080" - dependencies: - is-typedarray "^1.0.0" + integrity sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q= typedarray@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" + integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= typescript@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.0.3.tgz#4853b3e275ecdaa27f78fda46dc273a7eb7fc1c8" + version "3.3.3333" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.3.3333.tgz#171b2c5af66c59e9431199117a3bcadc66fdcfd6" + integrity sha512-JjSKsAfuHBE/fB2oZ8NxtRTk5iGcg6hkYXMnZ3Wc+b2RSqejEqTaem11mHASMnFilHrax3sLK0GDzcJrekZYLw== -uglify-es@^3.3.4: - version "3.3.9" - resolved "https://registry.yarnpkg.com/uglify-es/-/uglify-es-3.3.9.tgz#0c1c4f0700bed8dbc124cdb304d2592ca203e677" +unicode-canonical-property-names-ecmascript@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz#2619800c4c825800efdd8343af7dd9933cbe2818" + integrity sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ== + +unicode-match-property-ecmascript@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz#8ed2a32569961bce9227d09cd3ffbb8fed5f020c" + integrity sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg== dependencies: - commander "~2.13.0" - source-map "~0.6.1" + unicode-canonical-property-names-ecmascript "^1.0.4" + unicode-property-aliases-ecmascript "^1.0.4" -uglifyjs-webpack-plugin@^1.2.4: - version "1.3.0" - resolved "https://registry.yarnpkg.com/uglifyjs-webpack-plugin/-/uglifyjs-webpack-plugin-1.3.0.tgz#75f548160858163a08643e086d5fefe18a5d67de" - dependencies: - cacache "^10.0.4" - find-cache-dir "^1.0.0" - schema-utils "^0.4.5" - serialize-javascript "^1.4.0" - source-map "^0.6.1" - uglify-es "^3.3.4" - webpack-sources "^1.1.0" - worker-farm "^1.5.2" +unicode-match-property-value-ecmascript@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.1.0.tgz#5b4b426e08d13a80365e0d657ac7a6c1ec46a277" + integrity sha512-hDTHvaBk3RmFzvSl0UVrUmC3PuW9wKVnpoUDYH0JDkSIovzw+J5viQmeYHxVSBptubnr7PbH2e0fnpDRQnQl5g== -ultron@~1.1.0: - version "1.1.1" - resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.1.tgz#9fe1536a10a664a65266a1e3ccf85fd36302bc9c" - -unbzip2-stream@^1.0.9: - version "1.2.5" - resolved "https://registry.yarnpkg.com/unbzip2-stream/-/unbzip2-stream-1.2.5.tgz#73a033a567bbbde59654b193c44d48a7e4f43c47" - dependencies: - buffer "^3.0.1" - through "^2.3.6" - -underscore@1.8.3: - version "1.8.3" - resolved "https://registry.yarnpkg.com/underscore/-/underscore-1.8.3.tgz#4f3fb53b106e6097fcf9cb4109f2a5e9bdfa5022" +unicode-property-aliases-ecmascript@^1.0.4: + version "1.0.5" + resolved "https://registry.yarnpkg.com/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.0.5.tgz#a9cc6cc7ce63a0a3023fc99e341b94431d405a57" + integrity sha512-L5RAqCfXqAwR3RriF8pM0lU0w4Ryf/GgzONwi6KnL1taJQa7x1TCxdJnILX59WIGOwR57IVxn7Nej0fz1Ny6fw== union-value@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.0.tgz#5c71c34cb5bad5dcebe3ea0cd08207ba5aa1aea4" + integrity sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ= dependencies: arr-union "^3.1.0" get-value "^2.0.6" is-extendable "^0.1.1" set-value "^0.4.3" -unique-filename@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.0.tgz#d05f2fe4032560871f30e93cbe735eea201514f3" +unique-filename@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230" + integrity sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ== dependencies: unique-slug "^2.0.0" unique-slug@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.0.tgz#db6676e7c7cc0629878ff196097c78855ae9f4ab" + version "2.0.1" + resolved "https://registry.yarnpkg.com/unique-slug/-/unique-slug-2.0.1.tgz#5e9edc6d1ce8fb264db18a507ef9bd8544451ca6" + integrity sha512-n9cU6+gITaVu7VGj1Z8feKMmfAjEAQGhwD9fE3zvpRRa0wEIx8ODYkVGfSc94M2OX00tUFV8wH3zYbm1I8mxFg== dependencies: imurmurhash "^0.1.4" universalify@^0.1.0: version "0.1.2" resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" - -unpipe@1.0.0, unpipe@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" + integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== unset-value@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" + integrity sha1-g3aHP30jNRef+x5vw6jtDfyKtVk= dependencies: has-value "^0.3.1" isobject "^3.0.0" -upath@^1.0.5: - version "1.1.0" - resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.0.tgz#35256597e46a581db4793d0ce47fa9aebfc9fabd" +upath@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/upath/-/upath-1.1.1.tgz#497f7c1090b0818f310bbfb06783586a68d28014" + integrity sha512-D0yetkpIOKiZQquxjM2Syvy48Y1DbZ0SWxgsZiwd9GCWRpc75vN8ytzem14WDSg+oiX6+Qt31FpiS/ExODCrLg== uri-js@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.2.2.tgz#94c540e1ff772956e2299507c010aea6c8838eb0" + integrity sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ== dependencies: punycode "^2.1.0" urix@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" - -url-parse-lax@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-1.0.0.tgz#7af8f303645e9bd79a272e7a14ac68bc0609da73" - dependencies: - prepend-http "^1.0.1" - -url-set-query@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/url-set-query/-/url-set-query-1.0.0.tgz#016e8cfd7c20ee05cafe7795e892bd0702faa339" - -url-to-options@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/url-to-options/-/url-to-options-1.0.1.tgz#1505a03a289a48cbd7a434efbaeec5055f5633a9" + integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= url@0.11.0, url@^0.11.0: version "0.11.0" resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1" + integrity sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE= dependencies: punycode "1.3.2" querystring "0.2.0" @@ -4765,46 +4312,44 @@ url@0.11.0, url@^0.11.0: use@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" - -utf8@2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/utf8/-/utf8-2.1.1.tgz#2e01db02f7d8d0944f77104f1609eb0c304cf768" + integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== util-deprecate@~1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= + +util.promisify@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/util.promisify/-/util.promisify-1.0.0.tgz#440f7165a459c9a16dc145eb8e72f35687097030" + integrity sha512-i+6qA2MPhvoKLuxnJNpXAGhg7HphQOSUq2LKMZD0m15EiskXUkMvKdF4Uui0WYeCUGea+o2cw/ZuwehtfsrNkA== + dependencies: + define-properties "^1.1.2" + object.getownpropertydescriptors "^2.0.3" util@0.10.3: version "0.10.3" resolved "https://registry.yarnpkg.com/util/-/util-0.10.3.tgz#7afb1afe50805246489e3db7fe0ed379336ac0f9" + integrity sha1-evsa/lCAUkZInj23/g7TeTNqwPk= dependencies: inherits "2.0.1" -util@^0.10.3: - version "0.10.4" - resolved "https://registry.yarnpkg.com/util/-/util-0.10.4.tgz#3aa0125bfe668a4672de58857d3ace27ecb76901" +util@^0.11.0: + version "0.11.1" + resolved "https://registry.yarnpkg.com/util/-/util-0.11.1.tgz#3236733720ec64bb27f6e26f421aaa2e1b588d61" + integrity sha512-HShAsny+zS2TZfaXxD9tYj4HQGlBezXZMZuM/S5PKLLoZkShZiGk9o5CzukI1LVHZvjdvZ2Sj1aW/Ndn2NB/HQ== dependencies: inherits "2.0.3" -utils-merge@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" - -uuid@2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/uuid/-/uuid-2.0.1.tgz#c2a30dedb3e535d72ccf82e343941a50ba8533ac" - -uuid@^3.1.0, uuid@^3.3.2: +uuid@^3.1.0: version "3.3.2" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" - -vary@^1, vary@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" + integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA== verror@1.10.0: version "1.10.0" resolved "https://registry.yarnpkg.com/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400" + integrity sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA= dependencies: assert-plus "^1.0.0" core-util-is "1.0.2" @@ -4813,243 +4358,38 @@ verror@1.10.0: vm-browserify@0.0.4: version "0.0.4" resolved "https://registry.yarnpkg.com/vm-browserify/-/vm-browserify-0.0.4.tgz#5d7ea45bbef9e4a6ff65f95438e0a87c357d5a73" + integrity sha1-XX6kW7755Kb/ZflUOOCofDV9WnM= dependencies: indexof "0.0.1" watchpack@^1.5.0: version "1.6.0" resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.6.0.tgz#4bc12c2ebe8aa277a71f1d3f14d685c7b446cd00" + integrity sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA== dependencies: chokidar "^2.0.2" graceful-fs "^4.1.2" neo-async "^2.5.0" -web3-bzz@1.0.0-beta.36: - version "1.0.0-beta.36" - resolved "https://registry.yarnpkg.com/web3-bzz/-/web3-bzz-1.0.0-beta.36.tgz#adb3fe7a70053eb7843e32b106792b01b482ef41" - dependencies: - got "7.1.0" - swarm-js "0.1.37" - underscore "1.8.3" - -web3-core-helpers@1.0.0-beta.36: - version "1.0.0-beta.36" - resolved "https://registry.yarnpkg.com/web3-core-helpers/-/web3-core-helpers-1.0.0-beta.36.tgz#6f618e80f1a6588d846efbfdc28f92ae0477f8d2" - dependencies: - underscore "1.8.3" - web3-eth-iban "1.0.0-beta.36" - web3-utils "1.0.0-beta.36" - -web3-core-method@1.0.0-beta.36: - version "1.0.0-beta.36" - resolved "https://registry.yarnpkg.com/web3-core-method/-/web3-core-method-1.0.0-beta.36.tgz#855c0365ae7d0ead394d973ea9e28828602900e0" - dependencies: - underscore "1.8.3" - web3-core-helpers "1.0.0-beta.36" - web3-core-promievent "1.0.0-beta.36" - web3-core-subscriptions "1.0.0-beta.36" - web3-utils "1.0.0-beta.36" - -web3-core-promievent@1.0.0-beta.36: - version "1.0.0-beta.36" - resolved "https://registry.yarnpkg.com/web3-core-promievent/-/web3-core-promievent-1.0.0-beta.36.tgz#3a5127787fff751be6de272722cbc77dc9523fd5" - dependencies: - any-promise "1.3.0" - eventemitter3 "1.1.1" - -web3-core-requestmanager@1.0.0-beta.36: - version "1.0.0-beta.36" - resolved "https://registry.yarnpkg.com/web3-core-requestmanager/-/web3-core-requestmanager-1.0.0-beta.36.tgz#70c8eead84da9ed1cf258e6dde3f137116d0691b" - dependencies: - underscore "1.8.3" - web3-core-helpers "1.0.0-beta.36" - web3-providers-http "1.0.0-beta.36" - web3-providers-ipc "1.0.0-beta.36" - web3-providers-ws "1.0.0-beta.36" - -web3-core-subscriptions@1.0.0-beta.36: - version "1.0.0-beta.36" - resolved "https://registry.yarnpkg.com/web3-core-subscriptions/-/web3-core-subscriptions-1.0.0-beta.36.tgz#20f1f20c85d5b40f1e5a49b070ba977a142621f3" - dependencies: - eventemitter3 "1.1.1" - underscore "1.8.3" - web3-core-helpers "1.0.0-beta.36" - -web3-core@1.0.0-beta.36: - version "1.0.0-beta.36" - resolved "https://registry.yarnpkg.com/web3-core/-/web3-core-1.0.0-beta.36.tgz#86182f2456c2cf1cd6e7654d314e195eac211917" - dependencies: - web3-core-helpers "1.0.0-beta.36" - web3-core-method "1.0.0-beta.36" - web3-core-requestmanager "1.0.0-beta.36" - web3-utils "1.0.0-beta.36" - -web3-eth-abi@1.0.0-beta.36: - version "1.0.0-beta.36" - resolved "https://registry.yarnpkg.com/web3-eth-abi/-/web3-eth-abi-1.0.0-beta.36.tgz#21c0f222701db827a8a269accb9cd18bbd8f70f9" - dependencies: - ethers "4.0.0-beta.1" - underscore "1.8.3" - web3-utils "1.0.0-beta.36" - -web3-eth-accounts@1.0.0-beta.36: - version "1.0.0-beta.36" - resolved "https://registry.yarnpkg.com/web3-eth-accounts/-/web3-eth-accounts-1.0.0-beta.36.tgz#8aea37df9b038ef2c6cda608856ffd861b39eeef" - dependencies: - any-promise "1.3.0" - crypto-browserify "3.12.0" - eth-lib "0.2.7" - scrypt.js "0.2.0" - underscore "1.8.3" - uuid "2.0.1" - web3-core "1.0.0-beta.36" - web3-core-helpers "1.0.0-beta.36" - web3-core-method "1.0.0-beta.36" - web3-utils "1.0.0-beta.36" - -web3-eth-contract@1.0.0-beta.36: - version "1.0.0-beta.36" - resolved "https://registry.yarnpkg.com/web3-eth-contract/-/web3-eth-contract-1.0.0-beta.36.tgz#c0c366c4e4016896142208cee758a2ff2a31be2a" - dependencies: - underscore "1.8.3" - web3-core "1.0.0-beta.36" - web3-core-helpers "1.0.0-beta.36" - web3-core-method "1.0.0-beta.36" - web3-core-promievent "1.0.0-beta.36" - web3-core-subscriptions "1.0.0-beta.36" - web3-eth-abi "1.0.0-beta.36" - web3-utils "1.0.0-beta.36" - -web3-eth-ens@1.0.0-beta.36: - version "1.0.0-beta.36" - resolved "https://registry.yarnpkg.com/web3-eth-ens/-/web3-eth-ens-1.0.0-beta.36.tgz#c7440b42b597fd74f64bc402f03ad2e832f423d8" - dependencies: - eth-ens-namehash "2.0.8" - underscore "1.8.3" - web3-core "1.0.0-beta.36" - web3-core-helpers "1.0.0-beta.36" - web3-core-promievent "1.0.0-beta.36" - web3-eth-abi "1.0.0-beta.36" - web3-eth-contract "1.0.0-beta.36" - web3-utils "1.0.0-beta.36" - -web3-eth-iban@1.0.0-beta.36: - version "1.0.0-beta.36" - resolved "https://registry.yarnpkg.com/web3-eth-iban/-/web3-eth-iban-1.0.0-beta.36.tgz#00cb3aba7a5aeb15d02b07421042e263d7b2e01b" - dependencies: - bn.js "4.11.6" - web3-utils "1.0.0-beta.36" - -web3-eth-personal@1.0.0-beta.36: - version "1.0.0-beta.36" - resolved "https://registry.yarnpkg.com/web3-eth-personal/-/web3-eth-personal-1.0.0-beta.36.tgz#95545998a8ee377e3bb71e27c8d1a5dc1d7d5a21" - dependencies: - web3-core "1.0.0-beta.36" - web3-core-helpers "1.0.0-beta.36" - web3-core-method "1.0.0-beta.36" - web3-net "1.0.0-beta.36" - web3-utils "1.0.0-beta.36" - -web3-eth@1.0.0-beta.36: - version "1.0.0-beta.36" - resolved "https://registry.yarnpkg.com/web3-eth/-/web3-eth-1.0.0-beta.36.tgz#04a8c748d344c1accaa26d7d5d0eac0da7127f14" - dependencies: - underscore "1.8.3" - web3-core "1.0.0-beta.36" - web3-core-helpers "1.0.0-beta.36" - web3-core-method "1.0.0-beta.36" - web3-core-subscriptions "1.0.0-beta.36" - web3-eth-abi "1.0.0-beta.36" - web3-eth-accounts "1.0.0-beta.36" - web3-eth-contract "1.0.0-beta.36" - web3-eth-ens "1.0.0-beta.36" - web3-eth-iban "1.0.0-beta.36" - web3-eth-personal "1.0.0-beta.36" - web3-net "1.0.0-beta.36" - web3-utils "1.0.0-beta.36" - -web3-net@1.0.0-beta.36: - version "1.0.0-beta.36" - resolved "https://registry.yarnpkg.com/web3-net/-/web3-net-1.0.0-beta.36.tgz#396cd35cb40934ed022a1f44a8a642d3908c41eb" - dependencies: - web3-core "1.0.0-beta.36" - web3-core-method "1.0.0-beta.36" - web3-utils "1.0.0-beta.36" - -web3-providers-http@1.0.0-beta.36: - version "1.0.0-beta.36" - resolved "https://registry.yarnpkg.com/web3-providers-http/-/web3-providers-http-1.0.0-beta.36.tgz#c1937a2e64f8db7cd30f166794e37cf0fcca1131" - dependencies: - web3-core-helpers "1.0.0-beta.36" - xhr2-cookies "1.1.0" - -web3-providers-ipc@1.0.0-beta.36: - version "1.0.0-beta.36" - resolved "https://registry.yarnpkg.com/web3-providers-ipc/-/web3-providers-ipc-1.0.0-beta.36.tgz#0c78efb4ed6b0305ec830e1e0b785e61217ee605" - dependencies: - oboe "2.1.3" - underscore "1.8.3" - web3-core-helpers "1.0.0-beta.36" - -web3-providers-ws@1.0.0-beta.36: - version "1.0.0-beta.36" - resolved "https://registry.yarnpkg.com/web3-providers-ws/-/web3-providers-ws-1.0.0-beta.36.tgz#27b74082c7adfa0cb5a65535eb312e49008c97c3" - dependencies: - underscore "1.8.3" - web3-core-helpers "1.0.0-beta.36" - websocket "git://github.com/frozeman/WebSocket-Node.git#browserifyCompatible" - -web3-shh@1.0.0-beta.36: - version "1.0.0-beta.36" - resolved "https://registry.yarnpkg.com/web3-shh/-/web3-shh-1.0.0-beta.36.tgz#6ff297594480edefc710d9d287765a0c4a5d5af1" - dependencies: - web3-core "1.0.0-beta.36" - web3-core-method "1.0.0-beta.36" - web3-core-subscriptions "1.0.0-beta.36" - web3-net "1.0.0-beta.36" - -web3-utils@1.0.0-beta.36: - version "1.0.0-beta.36" - resolved "https://registry.yarnpkg.com/web3-utils/-/web3-utils-1.0.0-beta.36.tgz#dc19c9aeec009b1816cc91ef64d7fe9f8ee344c9" - dependencies: - bn.js "4.11.6" - eth-lib "0.1.27" - ethjs-unit "0.1.6" - number-to-bn "1.7.0" - randomhex "0.1.5" - underscore "1.8.3" - utf8 "2.1.1" - -web3@^1.0.0-beta.36: - version "1.0.0-beta.36" - resolved "https://registry.yarnpkg.com/web3/-/web3-1.0.0-beta.36.tgz#2954da9e431124c88396025510d840ba731c8373" - dependencies: - web3-bzz "1.0.0-beta.36" - web3-core "1.0.0-beta.36" - web3-eth "1.0.0-beta.36" - web3-eth-personal "1.0.0-beta.36" - web3-net "1.0.0-beta.36" - web3-shh "1.0.0-beta.36" - web3-utils "1.0.0-beta.36" - -webpack-sources@^1.1.0, webpack-sources@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.2.0.tgz#18181e0d013fce096faf6f8e6d41eeffffdceac2" +webpack-sources@^1.1.0, webpack-sources@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.3.0.tgz#2a28dcb9f1f45fe960d8f1493252b5ee6530fa85" + integrity sha512-OiVgSrbGu7NEnEvQJJgdSFPl2qWKkWq5lHMhgiToIiN9w34EBnjYzSYs+VbL5KoYiLNtFFa7BZIKxRED3I32pA== dependencies: source-list-map "^2.0.0" source-map "~0.6.1" -webpack@^4.0.0, webpack@^4.17.2: - version "4.17.2" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.17.2.tgz#49feb20205bd15f0a5f1fe0a12097d5d9931878d" +webpack@^4.17.2: + version "4.29.6" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.29.6.tgz#66bf0ec8beee4d469f8b598d3988ff9d8d90e955" + integrity sha512-MwBwpiE1BQpMDkbnUUaW6K8RFZjljJHArC6tWQJoFm0oQtfoSebtg4Y7/QHnJ/SddtjYLHaKGX64CFjG5rehJw== dependencies: - "@webassemblyjs/ast" "1.5.13" - "@webassemblyjs/helper-module-context" "1.5.13" - "@webassemblyjs/wasm-edit" "1.5.13" - "@webassemblyjs/wasm-opt" "1.5.13" - "@webassemblyjs/wasm-parser" "1.5.13" - acorn "^5.6.2" - acorn-dynamic-import "^3.0.0" + "@webassemblyjs/ast" "1.8.5" + "@webassemblyjs/helper-module-context" "1.8.5" + "@webassemblyjs/wasm-edit" "1.8.5" + "@webassemblyjs/wasm-parser" "1.8.5" + acorn "^6.0.5" + acorn-dynamic-import "^4.0.0" ajv "^6.1.0" ajv-keywords "^3.1.0" chrome-trace-event "^1.0.0" @@ -5063,124 +4403,64 @@ webpack@^4.0.0, webpack@^4.17.2: mkdirp "~0.5.0" neo-async "^2.5.0" node-libs-browser "^2.0.0" - schema-utils "^0.4.4" - tapable "^1.0.0" - uglifyjs-webpack-plugin "^1.2.4" + schema-utils "^1.0.0" + tapable "^1.1.0" + terser-webpack-plugin "^1.1.0" watchpack "^1.5.0" - webpack-sources "^1.2.0" - -"websocket@git://github.com/frozeman/WebSocket-Node.git#browserifyCompatible": - version "1.0.26" - resolved "git://github.com/frozeman/WebSocket-Node.git#6c72925e3f8aaaea8dc8450f97627e85263999f2" - dependencies: - debug "^2.2.0" - nan "^2.3.3" - typedarray-to-buffer "^3.1.2" - yaeti "^0.0.6" + webpack-sources "^1.3.0" which@^1.2.9: version "1.3.1" resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a" + integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ== dependencies: isexe "^2.0.0" wide-align@^1.1.0: version "1.1.3" resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.3.tgz#ae074e6bdc0c14a431e804e624549c633b000457" + integrity sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA== dependencies: string-width "^1.0.2 || 2" worker-farm@^1.5.2: version "1.6.0" resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.6.0.tgz#aecc405976fab5a95526180846f0dba288f3a4a0" + integrity sha512-6w+3tHbM87WnSWnENBUvA2pxJPLhQUg5LKwUQHq3r+XPhIM+Gh2R5ycbwPCyuGbNg+lPgdcnQUhuC02kJCvffQ== dependencies: errno "~0.1.7" wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - -ws@^3.0.0: - version "3.3.3" - resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2" - dependencies: - async-limiter "~1.0.0" - safe-buffer "~5.1.0" - ultron "~1.1.0" - -xhr-request-promise@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/xhr-request-promise/-/xhr-request-promise-0.1.2.tgz#343c44d1ee7726b8648069682d0f840c83b4261d" - dependencies: - xhr-request "^1.0.1" - -xhr-request@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/xhr-request/-/xhr-request-1.1.0.tgz#f4a7c1868b9f198723444d82dcae317643f2e2ed" - dependencies: - buffer-to-arraybuffer "^0.0.5" - object-assign "^4.1.1" - query-string "^5.0.1" - simple-get "^2.7.0" - timed-out "^4.0.1" - url-set-query "^1.0.0" - xhr "^2.0.4" - -xhr2-cookies@1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/xhr2-cookies/-/xhr2-cookies-1.1.0.tgz#7d77449d0999197f155cb73b23df72505ed89d48" - dependencies: - cookiejar "^2.1.1" - -xhr@^2.0.4, xhr@^2.3.3: - version "2.5.0" - resolved "https://registry.yarnpkg.com/xhr/-/xhr-2.5.0.tgz#bed8d1676d5ca36108667692b74b316c496e49dd" - dependencies: - global "~4.3.0" - is-function "^1.0.1" - parse-headers "^2.0.0" - xtend "^4.0.0" - -xmlhttprequest@1.8.0: - version "1.8.0" - resolved "https://registry.yarnpkg.com/xmlhttprequest/-/xmlhttprequest-1.8.0.tgz#67fe075c5c24fef39f9d65f5f7b7fe75171968fc" + integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= xtend@^4.0.0, xtend@~4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" + integrity sha1-pcbVMr5lbiPbgg77lDofBJmNY68= y18n@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.0.tgz#95ef94f85ecc81d007c264e190a120f0a3c8566b" - -yaeti@^0.0.6: - version "0.0.6" - resolved "https://registry.yarnpkg.com/yaeti/-/yaeti-0.0.6.tgz#f26f484d72684cf42bedfb76970aa1608fbf9577" - -yallist@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52" + integrity sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w== yallist@^3.0.0, yallist@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.2.tgz#8452b4bb7e83c7c188d8041c1a837c773d6d8bb9" + version "3.0.3" + resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.0.3.tgz#b4b049e314be545e3ce802236d6cd22cd91c3de9" + integrity sha512-S+Zk8DEWE6oKpV+vI3qWkaK+jSbIK86pCwe2IF/xwIpQ8jEuxpw9NyaGjmp9+BoJv5FV2piqCDcoCtStppiq2A== yauzl@2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.4.1.tgz#9528f442dab1b2284e58b4379bb194e22e0c4005" + integrity sha1-lSj0QtqxsihOWLQ3m7GU4i4MQAU= dependencies: fd-slicer "~1.0.1" yauzl@2.8.0: version "2.8.0" resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.8.0.tgz#79450aff22b2a9c5a41ef54e02db907ccfbf9ee2" + integrity sha1-eUUK/yKyqcWkHvVOAtuQfM+/nuI= dependencies: buffer-crc32 "~0.2.3" fd-slicer "~1.0.1" - -yauzl@^2.4.2: - version "2.10.0" - resolved "https://registry.yarnpkg.com/yauzl/-/yauzl-2.10.0.tgz#c7eb17c93e112cb1086fa6d8e51fb0667b79a5f9" - dependencies: - buffer-crc32 "~0.2.3" - fd-slicer "~1.1.0" From 9d0591918e698fb2f1847f9b7664eca3b306aa41 Mon Sep 17 00:00:00 2001 From: William O'Beirne Date: Wed, 13 Mar 2019 19:36:24 -0400 Subject: [PATCH 50/72] Require refund address or confirm donation (#354) * Add API check to disallow unsetting refund address. * Require either refund address to be set or explicit consent for a donation. Dont have them show up as refundable in admin. * Show donations on financials page * Continue after entry * Consider no_refund when checking for duplicate contribution. * Fix types. * Add a filter for all contributions that are considered donations. Update financial query to include donations. * Elaborate in message. --- .../components/ContributionDetail/index.tsx | 1 + admin/src/components/Financials/index.tsx | 7 +- admin/src/store.ts | 1 + admin/src/types.ts | 1 + admin/src/util/filters.ts | 5 + backend/grant/admin/views.py | 8 +- backend/grant/proposal/models.py | 18 ++- backend/grant/proposal/views.py | 9 +- backend/grant/user/views.py | 2 + backend/grant/utils/pagination.py | 18 ++- backend/migrations/versions/484abe10e157_.py | 29 +++++ frontend/client/api/api.ts | 11 +- .../ContributionModal/SetRefundAddress.less | 13 ++ .../ContributionModal/SetRefundAddress.tsx | 101 ++++++++++++++ .../components/ContributionModal/index.tsx | 123 +++++++++++++++--- .../Settings/Account/RefundAddress.tsx | 2 +- frontend/types/user.ts | 8 +- 17 files changed, 325 insertions(+), 32 deletions(-) create mode 100644 backend/migrations/versions/484abe10e157_.py create mode 100644 frontend/client/components/ContributionModal/SetRefundAddress.less create mode 100644 frontend/client/components/ContributionModal/SetRefundAddress.tsx diff --git a/admin/src/components/ContributionDetail/index.tsx b/admin/src/components/ContributionDetail/index.tsx index 61f9894f..767d24cc 100644 --- a/admin/src/components/ContributionDetail/index.tsx +++ b/admin/src/components/ContributionDetail/index.tsx @@ -137,6 +137,7 @@ class ContributionDetail extends React.Component { : N/A )} {renderDeetItem('staking tx', JSON.stringify(c.staking))} + {renderDeetItem('no refund', JSON.stringify(c.noRefund))} diff --git a/admin/src/components/Financials/index.tsx b/admin/src/components/Financials/index.tsx index e085475e..04343f4d 100644 --- a/admin/src/components/Financials/index.tsx +++ b/admin/src/components/Financials/index.tsx @@ -20,7 +20,7 @@ class Financials extends React.Component { return (
- + } height={180} /> + + + + ({ display: 'Refundable', color: '#afd500', group: 'Refundable', +}, { + id: 'DONATION', + display: 'Donations', + color: '#afd500', + group: 'Donations', }]); export const contributionFilters: Filters = { diff --git a/backend/grant/admin/views.py b/backend/grant/admin/views.py index 7443b1a5..9e663d9a 100644 --- a/backend/grant/admin/views.py +++ b/backend/grant/admin/views.py @@ -155,6 +155,7 @@ def stats(): contribution_refundable_count = db.session.query(func.count(ProposalContribution.id)) \ .filter(ProposalContribution.refund_tx_id == None) \ .filter(ProposalContribution.staking == False) \ + .filter(ProposalContribution.no_refund == False) \ .filter(ProposalContribution.status == ContributionStatus.CONFIRMED) \ .join(Proposal) \ .filter(or_( @@ -751,10 +752,13 @@ def financials(): 'funding': str(ex(sql_pc_p("pc.status = 'CONFIRMED' AND pc.staking = FALSE AND p.stage = 'FUNDING_REQUIRED'"))), 'funded': str(ex(sql_pc_p("pc.status = 'CONFIRMED' AND pc.staking = FALSE AND p.stage in ('WIP', 'COMPLETED')"))), 'refunding': str(ex(sql_pc_p( - "pc.status = 'CONFIRMED' AND pc.staking = FALSE AND pc.refund_tx_id IS NULL AND p.stage IN ('CANCELED', 'FAILED')" + "pc.status = 'CONFIRMED' AND pc.staking = FALSE AND pc.no_refund = FALSE AND pc.refund_tx_id IS NULL AND p.stage IN ('CANCELED', 'FAILED')" ))), 'refunded': str(ex(sql_pc_p( - "pc.status = 'CONFIRMED' AND pc.staking = FALSE AND pc.refund_tx_id IS NOT NULL AND p.stage IN ('CANCELED', 'FAILED')" + "pc.status = 'CONFIRMED' AND pc.staking = FALSE AND pc.no_refund = FALSE AND pc.refund_tx_id IS NOT NULL AND p.stage IN ('CANCELED', 'FAILED')" + ))), + 'donations': str(ex(sql_pc_p( + "(pc.status = 'CONFIRMED' AND pc.staking = FALSE AND pc.refund_tx_id IS NULL) AND (pc.no_refund = TRUE OR pc.user_id IS NULL) AND p.stage IN ('CANCELED', 'FAILED')" ))), 'gross': str(ex(sql_pc_p("pc.status = 'CONFIRMED' AND pc.refund_tx_id IS NULL"))), } diff --git a/backend/grant/proposal/models.py b/backend/grant/proposal/models.py index eb503aa4..68264d50 100644 --- a/backend/grant/proposal/models.py +++ b/backend/grant/proposal/models.py @@ -85,6 +85,7 @@ class ProposalContribution(db.Model): tx_id = db.Column(db.String(255), nullable=True) refund_tx_id = db.Column(db.String(255), nullable=True) staking = db.Column(db.Boolean, nullable=False) + no_refund = db.Column(db.Boolean, nullable=False) user = db.relationship("User") @@ -94,20 +95,23 @@ class ProposalContribution(db.Model): amount: str, user_id: int = None, staking: bool = False, + no_refund: bool = False, ): self.proposal_id = proposal_id self.amount = amount self.user_id = user_id self.staking = staking + self.no_refund = no_refund self.date_created = datetime.datetime.now() self.status = ContributionStatus.PENDING @staticmethod - def get_existing_contribution(user_id: int, proposal_id: int, amount: str): + def get_existing_contribution(user_id: int, proposal_id: int, amount: str, no_refund: bool = False): return ProposalContribution.query.filter_by( user_id=user_id, proposal_id=proposal_id, amount=amount, + no_refund=no_refund, status=ContributionStatus.PENDING, ).first() @@ -359,12 +363,19 @@ class Proposal(db.Model): self.set_contribution_matching(0) self.set_contribution_bounty('0') - def create_contribution(self, amount, user_id: int = None, staking: bool = False): + def create_contribution( + self, + amount, + user_id: int = None, + staking: bool = False, + no_refund: bool = False, + ): contribution = ProposalContribution( proposal_id=self.id, amount=amount, user_id=user_id, staking=staking, + no_refund=no_refund, ) db.session.add(contribution) db.session.flush() @@ -812,7 +823,8 @@ class AdminProposalContributionSchema(ma.Schema): "addresses", "refund_address", "refund_tx_id", - "staking" + "staking", + "no_refund", ) proposal = ma.Nested("ProposalSchema") diff --git a/backend/grant/proposal/views.py b/backend/grant/proposal/views.py index d4a857f3..4c7ecaf1 100644 --- a/backend/grant/proposal/views.py +++ b/backend/grant/proposal/views.py @@ -490,9 +490,10 @@ def get_proposal_contribution(proposal_id, contribution_id): # TODO add gaurd (minimum, maximum) @body({ "amount": fields.Str(required=True), - "anonymous": fields.Bool(required=False, missing=None) + "anonymous": fields.Bool(required=False, missing=None), + "noRefund": fields.Bool(required=False, missing=False), }) -def post_proposal_contribution(proposal_id, amount, anonymous): +def post_proposal_contribution(proposal_id, amount, anonymous, no_refund): proposal = Proposal.query.filter_by(id=proposal_id).first() if not proposal: return {"message": "No proposal matching id"}, 404 @@ -505,12 +506,14 @@ def post_proposal_contribution(proposal_id, amount, anonymous): user = get_authed_user() if user: - contribution = ProposalContribution.get_existing_contribution(user.id, proposal_id, amount) + contribution = ProposalContribution \ + .get_existing_contribution(user.id, proposal_id, amount, no_refund) if not contribution: code = 201 contribution = proposal.create_contribution( amount=amount, + no_refund=no_refund, user_id=user.id if user else None, ) diff --git a/backend/grant/user/views.py b/backend/grant/user/views.py index 72ea3dfe..c431e43d 100644 --- a/backend/grant/user/views.py +++ b/backend/grant/user/views.py @@ -357,6 +357,8 @@ def set_user_settings(user_id, email_subscriptions, refund_address): except ValidationException as e: return {"message": str(e)}, 400 + if refund_address == '' and g.current_user.settings.refund_address: + return {"message": "Refund address cannot be unset, only changed"}, 400 if refund_address: g.current_user.settings.refund_address = refund_address diff --git a/backend/grant/utils/pagination.py b/backend/grant/utils/pagination.py index fd4e23a9..5ce0935f 100644 --- a/backend/grant/utils/pagination.py +++ b/backend/grant/utils/pagination.py @@ -121,7 +121,7 @@ class ProposalPagination(Pagination): class ContributionPagination(Pagination): def __init__(self): self.FILTERS = [f'STATUS_{s}' for s in ContributionStatus.list()] - self.FILTERS.extend(['REFUNDABLE']) + self.FILTERS.extend(['REFUNDABLE', 'DONATION']) self.PAGE_SIZE = 9 self.SORT_MAP = { 'CREATED:DESC': ProposalContribution.date_created.desc(), @@ -153,6 +153,7 @@ class ContributionPagination(Pagination): if 'REFUNDABLE' in filters: query = query.filter(ProposalContribution.refund_tx_id == None) \ .filter(ProposalContribution.staking == False) \ + .filter(ProposalContribution.no_refund == False) \ .filter(ProposalContribution.status == ContributionStatus.CONFIRMED) \ .join(Proposal) \ .filter(or_( @@ -161,7 +162,20 @@ class ContributionPagination(Pagination): )) \ .join(ProposalContribution.user) \ .join(UserSettings) \ - .filter(UserSettings.refund_address != None) \ + .filter(UserSettings.refund_address != None) + + if 'DONATION' in filters: + query = query.filter(ProposalContribution.refund_tx_id == None) \ + .filter(ProposalContribution.status == ContributionStatus.CONFIRMED) \ + .filter(or_( + ProposalContribution.no_refund == True, + ProposalContribution.user_id == None, + )) \ + .join(Proposal) \ + .filter(or_( + Proposal.stage == ProposalStage.FAILED, + Proposal.stage == ProposalStage.CANCELED, + )) # SORT (see self.SORT_MAP) diff --git a/backend/migrations/versions/484abe10e157_.py b/backend/migrations/versions/484abe10e157_.py new file mode 100644 index 00000000..b640c805 --- /dev/null +++ b/backend/migrations/versions/484abe10e157_.py @@ -0,0 +1,29 @@ +"""Add no_refund column to contributions, default to false + +Revision ID: 484abe10e157 +Revises: 13365ffe910e +Create Date: 2019-03-13 16:35:01.520404 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.sql import expression + + +# revision identifiers, used by Alembic. +revision = '484abe10e157' +down_revision = '13365ffe910e' +branch_labels = None +depends_on = None + + +def upgrade(): +# ### commands auto generated by Alembic - please adjust! ### + op.add_column('proposal_contribution', sa.Column('no_refund', sa.Boolean(), nullable=False, server_default=expression.false())) + # ### end Alembic commands ### + + +def downgrade(): +# ### commands auto generated by Alembic - please adjust! ### + op.drop_column('proposal_contribution', 'no_refund') + # ### end Alembic commands ### diff --git a/frontend/client/api/api.ts b/frontend/client/api/api.ts index 0a52a027..0307f804 100644 --- a/frontend/client/api/api.ts +++ b/frontend/client/api/api.ts @@ -13,6 +13,7 @@ import { RFP, ProposalPageParams, PageParams, + UserSettings, } from 'types'; import { formatUserForPost, @@ -127,7 +128,9 @@ export function updateUser(user: User): Promise<{ data: User }> { return axios.put(`/api/v1/users/${user.userid}`, formatUserForPost(user)); } -export function getUserSettings(userId: string | number): Promise { +export function getUserSettings( + userId: string | number, +): Promise<{ data: UserSettings }> { return axios.get(`/api/v1/users/${userId}/settings`); } @@ -138,7 +141,7 @@ interface SettingsArgs { export function updateUserSettings( userId: string | number, args?: SettingsArgs, -): Promise { +): Promise<{ data: UserSettings }> { return axios.put(`/api/v1/users/${userId}/settings`, args); } @@ -313,11 +316,13 @@ export function putInviteResponse( export function postProposalContribution( proposalId: number, amount: string, - anonymous?: boolean, + anonymous: boolean = false, + noRefund: boolean = false, ): Promise<{ data: ContributionWithAddressesAndUser }> { return axios.post(`/api/v1/proposals/${proposalId}/contributions`, { amount, anonymous, + noRefund, }); } diff --git a/frontend/client/components/ContributionModal/SetRefundAddress.less b/frontend/client/components/ContributionModal/SetRefundAddress.less new file mode 100644 index 00000000..3b10e387 --- /dev/null +++ b/frontend/client/components/ContributionModal/SetRefundAddress.less @@ -0,0 +1,13 @@ +.SetRefundAddress { + .ant-alert { + margin-bottom: 1rem; + } + + .ant-form-item { + margin-bottom: 0.25rem; + + &.ant-form-item-with-help { + margin-bottom: 1rem; + } + } +} diff --git a/frontend/client/components/ContributionModal/SetRefundAddress.tsx b/frontend/client/components/ContributionModal/SetRefundAddress.tsx new file mode 100644 index 00000000..0667a5a3 --- /dev/null +++ b/frontend/client/components/ContributionModal/SetRefundAddress.tsx @@ -0,0 +1,101 @@ +import React from 'react'; +import { Form, Input, Button, message, Alert, Divider } from 'antd'; +import { updateUserSettings } from 'api/api'; +import { isValidAddress } from 'utils/validators'; +import './SetRefundAddress.less'; + +interface OwnProps { + userid: number; + onSetRefundAddress(): void; + onSetNoRefund(): void; +} + +type Props = OwnProps; + +const STATE = { + refundAddress: '', + isSaving: false, +}; + +type State = typeof STATE; + +export default class SetRefundAddress extends React.Component { + state: State = { ...STATE }; + + render() { + const { refundAddress, isSaving } = this.state; + + let status: 'validating' | 'error' | undefined; + let help; + if (refundAddress && !isValidAddress(refundAddress)) { + status = 'error'; + help = 'That doesn’t look like a valid address'; + } + + return ( +
+ + + + + + + + + or + + + + ); + } + + private handleChange = (ev: React.ChangeEvent) => { + this.setState({ refundAddress: ev.currentTarget.value }); + }; + + private handleSubmit = async (ev: React.FormEvent) => { + ev.preventDefault(); + const { userid } = this.props; + const { refundAddress } = this.state; + if (!refundAddress) { + return; + } + + this.setState({ isSaving: true }); + try { + await updateUserSettings(userid, { refundAddress }); + this.props.onSetRefundAddress(); + } catch (err) { + console.error(err); + message.error(err.message || err.toString(), 5); + this.setState({ isSaving: false }); + } + }; +} diff --git a/frontend/client/components/ContributionModal/index.tsx b/frontend/client/components/ContributionModal/index.tsx index c82a8386..dea0d397 100644 --- a/frontend/client/components/ContributionModal/index.tsx +++ b/frontend/client/components/ContributionModal/index.tsx @@ -1,10 +1,17 @@ import React from 'react'; +import { connect } from 'react-redux'; import { Link } from 'react-router-dom'; import { Modal, Alert } from 'antd'; import Result from 'ant-design-pro/lib/Result'; -import { postProposalContribution, getProposalContribution } from 'api/api'; +import { + postProposalContribution, + getProposalContribution, + getUserSettings, +} from 'api/api'; import { ContributionWithAddressesAndUser } from 'types'; import PaymentInfo from './PaymentInfo'; +import SetRefundAddress from './SetRefundAddress'; +import { AppState } from 'store/reducers'; interface OwnProps { isVisible: boolean; @@ -18,21 +25,29 @@ interface OwnProps { handleClose(): void; } -type Props = OwnProps; +interface StateProps { + authUser: AppState['auth']['user']; +} + +type Props = StateProps & OwnProps; interface State { hasConfirmedAnonymous: boolean; hasSent: boolean; contribution: ContributionWithAddressesAndUser | null; + needsRefundAddress: boolean; + noRefund: boolean; isFetchingContribution: boolean; error: string | null; } -export default class ContributionModal extends React.Component { +class ContributionModal extends React.Component { state: State = { hasConfirmedAnonymous: false, hasSent: false, contribution: null, + needsRefundAddress: false, + noRefund: false, isFetchingContribution: false, error: null, }; @@ -47,7 +62,7 @@ export default class ContributionModal extends React.Component { } } - componentWillUpdate(nextProps: Props) { + componentWillUpdate(nextProps: Props, nextState: State) { const { isVisible, isAnonymous, @@ -59,7 +74,7 @@ export default class ContributionModal extends React.Component { // But not if we're anonymous, that will happen in confirmAnonymous if (isVisible && proposalId && !isAnonymous) { if (this.props.isVisible !== isVisible || proposalId !== this.props.proposalId) { - this.fetchAddresses(proposalId, contributionId); + this.fetchAddresses(proposalId, contributionId, nextState.noRefund); } } // If contribution is provided, update it @@ -72,19 +87,37 @@ export default class ContributionModal extends React.Component { contribution: null, hasConfirmedAnonymous: false, hasSent: false, + needsRefundAddress: false, + noRefund: false, error: null, }); } } render() { - const { isVisible, isAnonymous, handleClose, hasNoButtons, text } = this.props; - const { hasSent, hasConfirmedAnonymous, contribution, error } = this.state; + const { isVisible, isAnonymous, handleClose, text, authUser } = this.props; + const { + hasSent, + hasConfirmedAnonymous, + needsRefundAddress, + contribution, + error, + } = this.state; + let { hasNoButtons } = this.props; let okText; let onOk; let content; - if (isAnonymous && !hasConfirmedAnonymous) { + if (needsRefundAddress && authUser) { + hasNoButtons = true; + content = ( + + ); + } else if (isAnonymous && !hasConfirmedAnonymous) { okText = 'I accept'; onOk = this.confirmAnonymous; content = ( @@ -181,15 +214,38 @@ export default class ContributionModal extends React.Component { ); } - private async fetchAddresses(proposalId: number, contributionId?: number) { + private async fetchAddresses( + proposalId: number, + contributionId?: number, + noRefund?: boolean, + ) { this.setState({ isFetchingContribution: true }); try { - const { amount, isAnonymous } = this.props; + const { amount, isAnonymous, authUser } = this.props; + + // Ensure auth'd users have a refund address unless they've confirmed + if (!isAnonymous && !noRefund) { + // This should never happen, but make Typescript happy + if (!authUser) { + throw new Error('You must be logged in to contribute non-anonymously'); + } + const { data: settings } = await getUserSettings(authUser.userid); + if (!settings.refundAddress) { + this.setState({ needsRefundAddress: true }); + return; + } + } + let res; if (contributionId) { res = await getProposalContribution(proposalId, contributionId); } else { - res = await postProposalContribution(proposalId, amount || '0', isAnonymous); + res = await postProposalContribution( + proposalId, + amount || '0', + isAnonymous, + noRefund, + ); } this.setState({ contribution: res.data }); } catch (err) { @@ -199,14 +255,49 @@ export default class ContributionModal extends React.Component { } private confirmAnonymous = () => { - const { state, props } = this; - this.setState({ hasConfirmedAnonymous: true }); - if (!state.contribution && !props.contribution && props.proposalId) { - this.fetchAddresses(props.proposalId, props.contributionId); - } + this.setState({ hasConfirmedAnonymous: true }, () => { + const { state, props } = this; + if (!state.contribution && !props.contribution && props.proposalId) { + this.fetchAddresses(props.proposalId, props.contributionId, state.noRefund); + } + }); }; private confirmSend = () => { this.setState({ hasSent: true }); }; + + private confirmRefundAddressSet = () => { + this.setState( + { + needsRefundAddress: false, + noRefund: false, + }, + () => { + const { state, props } = this; + if (!state.contribution && !props.contribution && props.proposalId) { + this.fetchAddresses(props.proposalId, props.contributionId, state.noRefund); + } + }, + ); + }; + + private confirmNoRefund = () => { + this.setState( + { + needsRefundAddress: false, + noRefund: true, + }, + () => { + const { state, props } = this; + if (!state.contribution && !props.contribution && props.proposalId) { + this.fetchAddresses(props.proposalId, props.contributionId, state.noRefund); + } + }, + ); + }; } + +export default connect(state => ({ + authUser: state.auth.user, +}))(ContributionModal); diff --git a/frontend/client/components/Settings/Account/RefundAddress.tsx b/frontend/client/components/Settings/Account/RefundAddress.tsx index 95064bd8..77b9d62c 100644 --- a/frontend/client/components/Settings/Account/RefundAddress.tsx +++ b/frontend/client/components/Settings/Account/RefundAddress.tsx @@ -92,7 +92,7 @@ class RefundAddress extends React.Component { try { const res = await updateUserSettings(userid, { refundAddress }); message.success('Settings saved'); - this.setState({ refundAddress: res.data.refundAddress }); + this.setState({ refundAddress: res.data.refundAddress || '' }); } catch (err) { console.error(err); message.error(err.message || err.toString(), 5); diff --git a/frontend/types/user.ts b/frontend/types/user.ts index 85a122f0..0e2d3fb0 100644 --- a/frontend/types/user.ts +++ b/frontend/types/user.ts @@ -1,4 +1,5 @@ -import { SocialMedia } from 'types'; +import { SocialMedia } from './social'; +import { EmailSubscriptions } from './email'; export interface User { userid: number; @@ -9,3 +10,8 @@ export interface User { socialMedias: SocialMedia[]; avatar: { imageUrl: string } | null; } + +export interface UserSettings { + emailSubscriptions: EmailSubscriptions; + refundAddress?: string | null; +} From 21a2a1f6ba9c83d43c9c63555334897c28e9f700 Mon Sep 17 00:00:00 2001 From: Daniel Ternyak Date: Wed, 13 Mar 2019 18:36:53 -0500 Subject: [PATCH 51/72] Add explanation for staking (#356) --- frontend/client/components/Profile/ProfilePending.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/client/components/Profile/ProfilePending.tsx b/frontend/client/components/Profile/ProfilePending.tsx index b27e94f3..8d4e4d24 100644 --- a/frontend/client/components/Profile/ProfilePending.tsx +++ b/frontend/client/components/Profile/ProfilePending.tsx @@ -141,7 +141,7 @@ class ProfilePending extends React.Component { handleClose={this.closeStakingModal} text={

- Please send the staking contribution of{' '} + For your proposal to be considered, please send a staking contribution of{' '} {stakeContribution && stakeContribution.amount} ZEC using the instructions below. Once your payment has been sent and received 6 confirmations, you will receive an email. From a91269f6058109a9b91ef7e30b603b5280645bd5 Mon Sep 17 00:00:00 2001 From: William O'Beirne Date: Thu, 14 Mar 2019 01:05:02 -0400 Subject: [PATCH 52/72] Replace email logo (#357) --- backend/grant/templates/emails/template.html | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backend/grant/templates/emails/template.html b/backend/grant/templates/emails/template.html index 12d099f9..dd5aa672 100644 --- a/backend/grant/templates/emails/template.html +++ b/backend/grant/templates/emails/template.html @@ -91,9 +91,9 @@ - Logo + ZF Grants logo From 1130d9f3be722eac7411db335c1b2b6caf28334f Mon Sep 17 00:00:00 2001 From: William O'Beirne Date: Thu, 14 Mar 2019 01:06:02 -0400 Subject: [PATCH 53/72] Slugify RFP urls (#358) --- frontend/client/components/CreateFlow/Basics.tsx | 2 +- frontend/client/components/Proposal/RFPBlock/index.tsx | 2 +- frontend/client/components/RFPs/RFPItem.tsx | 4 ++-- frontend/client/utils/api.ts | 1 + frontend/types/rfp.ts | 1 + 5 files changed, 6 insertions(+), 4 deletions(-) diff --git a/frontend/client/components/CreateFlow/Basics.tsx b/frontend/client/components/CreateFlow/Basics.tsx index ab064d83..08381fc5 100644 --- a/frontend/client/components/CreateFlow/Basics.tsx +++ b/frontend/client/components/CreateFlow/Basics.tsx @@ -81,7 +81,7 @@ class CreateFlowBasics extends React.Component { description={ <> This proposal is for the open request{' '} - + {rfp.title} . If you didn’t mean to do this, or want to unlink it,{' '} diff --git a/frontend/client/components/Proposal/RFPBlock/index.tsx b/frontend/client/components/Proposal/RFPBlock/index.tsx index c7335915..4f608cb5 100644 --- a/frontend/client/components/Proposal/RFPBlock/index.tsx +++ b/frontend/client/components/Proposal/RFPBlock/index.tsx @@ -12,7 +12,7 @@ const RFPBlock: React.SFC = ({ rfp }) => {

Request

- +

{rfp.title}

{rfp.brief}
diff --git a/frontend/client/components/RFPs/RFPItem.tsx b/frontend/client/components/RFPs/RFPItem.tsx index 7645b3d5..8863227a 100644 --- a/frontend/client/components/RFPs/RFPItem.tsx +++ b/frontend/client/components/RFPs/RFPItem.tsx @@ -16,7 +16,7 @@ export default class RFPItem extends React.Component { render() { const { rfp, isSmall } = this.props; const { - id, + urlId, title, brief, acceptedProposals, @@ -49,7 +49,7 @@ export default class RFPItem extends React.Component { return (

{title} diff --git a/frontend/client/utils/api.ts b/frontend/client/utils/api.ts index 98fb3fb5..0d0f4a89 100644 --- a/frontend/client/utils/api.ts +++ b/frontend/client/utils/api.ts @@ -105,6 +105,7 @@ export function formatProposalFromGet(p: any): Proposal { } export function formatRFPFromGet(rfp: RFP): RFP { + rfp.urlId = generateSlugUrl(rfp.id, rfp.title); if (rfp.bounty) { rfp.bounty = toZat(rfp.bounty as any); } diff --git a/frontend/types/rfp.ts b/frontend/types/rfp.ts index 76035a54..ad1f72eb 100644 --- a/frontend/types/rfp.ts +++ b/frontend/types/rfp.ts @@ -4,6 +4,7 @@ import { Zat } from 'utils/units'; export interface RFP { id: number; + urlId: string; title: string; brief: string; content: string; From 0069de7fc450e8dc307fe446a847bd47fce0b0bb Mon Sep 17 00:00:00 2001 From: William O'Beirne Date: Thu, 14 Mar 2019 01:06:22 -0400 Subject: [PATCH 54/72] HeaderDetails on proposals and RFPs (#359) * Set header details on proposal detail pages. * Add RFP header details --- frontend/client/components/Proposal/index.tsx | 2 ++ frontend/client/components/RFP/index.tsx | 2 ++ 2 files changed, 4 insertions(+) diff --git a/frontend/client/components/Proposal/index.tsx b/frontend/client/components/Proposal/index.tsx index 2fc836da..9c3e9927 100644 --- a/frontend/client/components/Proposal/index.tsx +++ b/frontend/client/components/Proposal/index.tsx @@ -12,6 +12,7 @@ import { STATUS } from 'types'; import { Tabs, Icon, Dropdown, Menu, Button, Alert } from 'antd'; import { AlertProps } from 'antd/lib/alert'; import ExceptionPage from 'components/ExceptionPage'; +import HeaderDetails from 'components/HeaderDetails'; import CampaignBlock from './CampaignBlock'; import TeamBlock from './TeamBlock'; import RFPBlock from './RFPBlock'; @@ -164,6 +165,7 @@ export class ProposalDetail extends React.Component { return (
+ {banner && (
diff --git a/frontend/client/components/RFP/index.tsx b/frontend/client/components/RFP/index.tsx index 44642334..1278f825 100644 --- a/frontend/client/components/RFP/index.tsx +++ b/frontend/client/components/RFP/index.tsx @@ -12,6 +12,7 @@ import Loader from 'components/Loader'; import Markdown from 'components/Markdown'; import ProposalCard from 'components/Proposals/ProposalCard'; import UnitDisplay from 'components/UnitDisplay'; +import HeaderDetails from 'components/HeaderDetails'; import './index.less'; interface OwnProps { @@ -67,6 +68,7 @@ class RFPDetail extends React.Component { return (
+
Back to Requests From 46aa7cf6cfd08498edf3a102eae549750dbfe7eb Mon Sep 17 00:00:00 2001 From: AMStrix Date: Thu, 14 Mar 2019 11:46:09 -0500 Subject: [PATCH 55/72] Limit Contribution Related Emails (#360) * BE: limit contribution emails to CONFIRMED & one per user + add app context to EmailSender * BE: handle EmailSender setup and start using flask request context --- backend/grant/app.py | 9 +++- backend/grant/email/send.py | 33 ++++++------- backend/grant/proposal/models.py | 26 +++++----- backend/grant/proposal/views.py | 20 ++++---- backend/grant/task/jobs.py | 21 ++++----- .../templates/emails/contribution_update.html | 47 +++++++++++-------- .../templates/emails/contribution_update.txt | 2 +- 7 files changed, 85 insertions(+), 73 deletions(-) diff --git a/backend/grant/app.py b/backend/grant/app.py index 65c2b094..399c1b53 100644 --- a/backend/grant/app.py +++ b/backend/grant/app.py @@ -4,7 +4,7 @@ import sentry_sdk import logging import traceback from animal_case import animalify -from flask import Flask, Response, jsonify, request +from flask import Flask, Response, jsonify, request, g from flask_cors import CORS from flask_security import SQLAlchemyUserDatastore from flask_sslify import SSLify @@ -32,6 +32,13 @@ def create_app(config_objects=["grant.settings"]): app = Flask(__name__.split(".")[0]) app.response_class = JSONResponse + @app.after_request + def send_emails(response): + if 'email_sender' in g: + # starting email sender + g.email_sender.start() + return response + # Return validation errors @app.errorhandler(ValidationException) def handle_validation_error(err): diff --git a/backend/grant/email/send.py b/backend/grant/email/send.py index dcc4d737..8fdd611f 100644 --- a/backend/grant/email/send.py +++ b/backend/grant/email/send.py @@ -7,7 +7,7 @@ from grant.settings import SENDGRID_API_KEY, SENDGRID_DEFAULT_FROM, SENDGRID_DEF from grant.settings import SENDGRID_API_KEY, SENDGRID_DEFAULT_FROM, UI, E2E_TESTING import sendgrid from threading import Thread -from flask import render_template, Markup, current_app +from flask import render_template, Markup, current_app, g default_template_args = { @@ -351,9 +351,9 @@ def generate_email(type, email_args, user=None): def send_email(to, type, email_args): - mail = make_envelope(to, type, email_args) - if mail: - sendgrid_send(mail) + if 'email_sender' not in g: + g.email_sender = EmailSender(current_app._get_current_object()) + g.email_sender.add(to, type, email_args) def make_envelope(to, type, email_args): @@ -385,7 +385,7 @@ def make_envelope(to, type, email_args): return mail -def sendgrid_send(mail): +def sendgrid_send(mail, app=current_app): to = mail.___to type = mail.___type try: @@ -393,27 +393,28 @@ def sendgrid_send(mail): if E2E_TESTING: from grant.e2e import views views.last_email = mail.get() - current_app.logger.info(f'Just set last_email for e2e to pickup, to: {to}, type: {type}') + app.logger.info(f'Just set last_email for e2e to pickup, to: {to}, type: {type}') else: res = sg.client.mail.send.post(request_body=mail.get()) - current_app.logger.info('Just sent an email to %s of type %s, response code: %s' % - (to, type, res.status_code)) + app.logger.info('Just sent an email to %s of type %s, response code: %s' % + (to, type, res.status_code)) except HTTPError as e: - current_app.logger.info('An HTTP error occured while sending an email to %s - %s: %s' % - (to, e.__class__.__name__, e)) - current_app.logger.debug(e.body) + app.logger.info('An HTTP error occured while sending an email to %s - %s: %s' % + (to, e.__class__.__name__, e)) + app.logger.debug(e.body) capture_exception(e) except Exception as e: - current_app.logger.info('An unknown error occured while sending an email to %s - %s: %s' % - (to, e.__class__.__name__, e)) - current_app.logger.debug(e) + app.logger.info('An unknown error occured while sending an email to %s - %s: %s' % + (to, e.__class__.__name__, e)) + app.logger.debug(e) capture_exception(e) class EmailSender(Thread): - def __init__(self): + def __init__(self, app): Thread.__init__(self) self.envelopes = [] + self.app = app def add(self, to, type, email_args): env = make_envelope(to, type, email_args) @@ -422,4 +423,4 @@ class EmailSender(Thread): def run(self): for envelope in self.envelopes: - sendgrid_send(envelope) + sendgrid_send(envelope, self.app) diff --git a/backend/grant/proposal/models.py b/backend/grant/proposal/models.py index 68264d50..c435f73c 100644 --- a/backend/grant/proposal/models.py +++ b/backend/grant/proposal/models.py @@ -5,8 +5,9 @@ from sqlalchemy.ext.hybrid import hybrid_property from decimal import Decimal from marshmallow import post_dump +from flask import current_app from grant.comment.models import Comment -from grant.email.send import send_email, EmailSender +from grant.email.send import send_email from grant.extensions import ma, db from grant.utils.exceptions import ValidationException from grant.utils.misc import dt_to_unix, make_url, gen_random_id @@ -524,21 +525,17 @@ class Proposal(db.Model): db.session.flush() # Send emails to team & contributors - email_sender = EmailSender() for u in self.team: - email_sender.add(u.email_address, 'proposal_canceled', { + send_email(u.email_address, 'proposal_canceled', { 'proposal': self, 'support_url': make_url('/contact'), }) - for c in self.contributions: - if c.user: - email_sender.add(c.user.email_address, 'contribution_proposal_canceled', { - 'contribution': c, - 'proposal': self, - 'refund_address': c.user.settings.refund_address, - 'account_settings_url': make_url('/profile/settings?tab=account') - }) - email_sender.start() + for u in self.contributors: + send_email(u.email_address, 'contribution_proposal_canceled', { + 'proposal': self, + 'refund_address': u.settings.refund_address, + 'account_settings_url': make_url('/profile/settings?tab=account') + }) @hybrid_property def contributed(self): @@ -602,6 +599,11 @@ class Proposal(db.Model): return self.milestones[-1] # return last one if all PAID return None + @hybrid_property + def contributors(self): + d = {c.user.id: c.user for c in self.contributions if c.user and c.status == ContributionStatus.CONFIRMED} + return d.values() + class ProposalSchema(ma.Schema): class Meta: diff --git a/backend/grant/proposal/views.py b/backend/grant/proposal/views.py index 4c7ecaf1..354e8369 100644 --- a/backend/grant/proposal/views.py +++ b/backend/grant/proposal/views.py @@ -8,7 +8,7 @@ from sentry_sdk import capture_message from grant.extensions import limiter from grant.comment.models import Comment, comment_schema, comments_schema -from grant.email.send import send_email, EmailSender +from grant.email.send import send_email from grant.milestone.models import Milestone from grant.parser import body, query, paginated_fields from grant.rfp.models import RFP @@ -370,17 +370,13 @@ def post_proposal_update(proposal_id, title, content): db.session.add(update) db.session.commit() - # Send email to all contributors (even if contribution failed) - email_sender = EmailSender() - contributions = ProposalContribution.query.filter_by(proposal_id=proposal_id).all() - for c in contributions: - if c.user: - email_sender.add(c.user.email_address, 'contribution_update', { - 'proposal': g.current_proposal, - 'proposal_update': update, - 'update_url': make_url(f'/proposals/{proposal_id}?tab=updates&update={update.id}'), - }) - email_sender.start() + # Send email to all contributors + for u in g.current_proposal.contributors: + send_email(u.email_address, 'contribution_update', { + 'proposal': g.current_proposal, + 'proposal_update': update, + 'update_url': make_url(f'/proposals/{proposal_id}?tab=updates&update={update.id}'), + }) dumped_update = proposal_update_schema.dump(update) return dumped_update, 201 diff --git a/backend/grant/task/jobs.py b/backend/grant/task/jobs.py index ef2e96b2..ae148f1e 100644 --- a/backend/grant/task/jobs.py +++ b/backend/grant/task/jobs.py @@ -1,9 +1,10 @@ from datetime import datetime, timedelta from grant.extensions import db -from grant.email.send import send_email, EmailSender +from grant.email.send import send_email from grant.utils.enums import ProposalStage, ContributionStatus from grant.utils.misc import make_url +from flask import current_app class ProposalReminder: @@ -72,20 +73,16 @@ class ProposalDeadline: db.session.commit() # Send emails to team & contributors - email_sender = EmailSender() for u in proposal.team: - email_sender.add(u.email_address, 'proposal_failed', { + send_email(u.email_address, 'proposal_failed', { 'proposal': proposal, }) - for c in proposal.contributions: - if c.user: - email_sender.add(c.user.email_address, 'contribution_proposal_failed', { - 'contribution': c, - 'proposal': proposal, - 'refund_address': c.user.settings.refund_address, - 'account_settings_url': make_url('/profile/settings?tab=account') - }) - email_sender.start() + for u in proposal.contributors: + send_email(u.email_address, 'contribution_proposal_failed', { + 'proposal': proposal, + 'refund_address': u.settings.refund_address, + 'account_settings_url': make_url('/profile/settings?tab=account') + }) class ContributionExpired: diff --git a/backend/grant/templates/emails/contribution_update.html b/backend/grant/templates/emails/contribution_update.html index 6a0855d2..3c89f48d 100644 --- a/backend/grant/templates/emails/contribution_update.html +++ b/backend/grant/templates/emails/contribution_update.html @@ -1,24 +1,33 @@

- A proposal you follow, {{ args.proposal.title }}, has - posted an update entitled "{{ args.proposal_update.title }}" + A proposal you contributed to, {{ args.proposal.title }}, has posted an update entitled "{{ + args.proposal_update.title + }}"

- - - + + +
- - - - -
- - Read the Update - -
-
+ + + + +
+ + Read the Update + +
+
diff --git a/backend/grant/templates/emails/contribution_update.txt b/backend/grant/templates/emails/contribution_update.txt index 384b05ae..fe4936db 100644 --- a/backend/grant/templates/emails/contribution_update.txt +++ b/backend/grant/templates/emails/contribution_update.txt @@ -1,4 +1,4 @@ -A proposal you follow, "{{ args.proposal.title }}", has posted an update +A proposal you contributed to, "{{ args.proposal.title }}", has posted an update entitled "{{ args.proposal_update.title }}". Go here to read it: {{ args.update_url }} \ No newline at end of file From d4368b2f25a8231f1f3336e8e4e8ee2c938cbf75 Mon Sep 17 00:00:00 2001 From: AMStrix Date: Thu, 14 Mar 2019 11:47:42 -0500 Subject: [PATCH 56/72] admin: proposal published shows 'n/a' when datePublished is 0 (#361) --- admin/src/components/ProposalDetail/index.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/admin/src/components/ProposalDetail/index.tsx b/admin/src/components/ProposalDetail/index.tsx index 65f6f4df..2067569f 100644 --- a/admin/src/components/ProposalDetail/index.tsx +++ b/admin/src/components/ProposalDetail/index.tsx @@ -422,7 +422,10 @@ class ProposalDetailNaked extends React.Component { {renderDeetItem('id', p.proposalId)} {renderDeetItem('created', formatDateSeconds(p.dateCreated))} - {renderDeetItem('published', formatDateSeconds(p.datePublished))} + {renderDeetItem( + 'published', + p.datePublished ? formatDateSeconds(p.datePublished) : 'n/a', + )} {renderDeetItem( 'deadlineDuration', formatDurationSeconds(p.deadlineDuration), From 5d7990a175a802e2be9fb5889517d8ce20bdf536 Mon Sep 17 00:00:00 2001 From: William O'Beirne Date: Thu, 14 Mar 2019 15:37:50 -0400 Subject: [PATCH 57/72] Testnet banner (#362) * Scaffolding for banner * Layout one * Hide on homepage. * Invert conditional --- frontend/.env.example | 3 ++ frontend/client/components/Header/index.tsx | 6 ++++ frontend/client/components/Header/style.less | 31 ++++++++++++++++++++ frontend/config/env.js | 3 +- 4 files changed, 42 insertions(+), 1 deletion(-) diff --git a/frontend/.env.example b/frontend/.env.example index 81570ae7..29dc88b1 100644 --- a/frontend/.env.example +++ b/frontend/.env.example @@ -21,3 +21,6 @@ PROPOSAL_STAKING_AMOUNT=0.025 # Normally production runs with SSL, this disables that DISABLE_SSL=true + +# Uncomment if running on testnet +# TESTNET=true diff --git a/frontend/client/components/Header/index.tsx b/frontend/client/components/Header/index.tsx index 8830d6ce..8a8adafc 100644 --- a/frontend/client/components/Header/index.tsx +++ b/frontend/client/components/Header/index.tsx @@ -59,6 +59,12 @@ export default class Header extends React.Component {
+ + {process.env.TESTNET && ( +
+ Testnet +
+ )}
); diff --git a/frontend/client/components/Header/style.less b/frontend/client/components/Header/style.less index 84a85343..9b803586 100644 --- a/frontend/client/components/Header/style.less +++ b/frontend/client/components/Header/style.less @@ -45,6 +45,7 @@ flex-grow: 1; text-align: center; opacity: 0.7; + z-index: 222; &:hover, &:focus { @@ -121,4 +122,34 @@ } } } + + &-testnet { + position: absolute; + bottom: 0; + left: 50%; + height: 1.4rem; + line-height: 1.4rem; + padding: 0 0.5rem; + transform: translate(-50%, 50%); + text-align: center; + border-radius: 1rem; + box-shadow: 0 0 0 1px rgba(#000, 0.1); + color: rgba(#000, 0.6); + background: #FFF; + text-transform: uppercase; + letter-spacing: 0.9rem; + font-size: 0.65rem; + font-weight: bold; + transition: opacity @header-transition ease; + z-index: 111; + + > span { + display: inline-block; + transform: translateX(0.42rem); + } + + .is-transparent & { + opacity: 0; + } + } } diff --git a/frontend/config/env.js b/frontend/config/env.js index 1e4813f7..06eaced3 100644 --- a/frontend/config/env.js +++ b/frontend/config/env.js @@ -53,13 +53,14 @@ process.env.NODE_PATH = (process.env.NODE_PATH || '') module.exports = () => { const raw = { BACKEND_URL: process.env.BACKEND_URL || 'http://localhost:5000', - EXPLORER_URL: process.env.EXPLORER_URL || 'https://chain.so/zcash/', + EXPLORER_URL: process.env.EXPLORER_URL || 'https://explorer.zcha.in/', NODE_ENV: process.env.NODE_ENV || 'development', PORT: process.env.PORT || 3000, PROPOSAL_STAKING_AMOUNT: process.env.PROPOSAL_STAKING_AMOUNT, PUBLIC_HOST_URL: process.env.PUBLIC_HOST_URL, SENTRY_DSN: process.env.SENTRY_DSN || null, SENTRY_RELEASE: process.env.SENTRY_RELEASE || undefined, + TESTNET: process.env.TESTNET || false, }; // Stringify all values so we can feed into Webpack DefinePlugin From ff3d0480831df775b592271f258c6639d0f8733a Mon Sep 17 00:00:00 2001 From: Daniel Ternyak Date: Thu, 14 Mar 2019 15:07:32 -0500 Subject: [PATCH 58/72] Consolidate migrations (#363) --- backend/migrations/versions/02acd43b4357_.py | 30 --- backend/migrations/versions/0f08974b4118_.py | 247 +++++++++++++++++++ backend/migrations/versions/13365ffe910e_.py | 31 --- backend/migrations/versions/27975c4a04a4_.py | 32 --- backend/migrations/versions/310dca400b81_.py | 30 --- backend/migrations/versions/332a15eba9d8_.py | 26 -- backend/migrations/versions/3514aaf4648f_.py | 29 --- backend/migrations/versions/3793d9a71e27_.py | 52 ---- backend/migrations/versions/484abe10e157_.py | 29 --- backend/migrations/versions/4af29f8b2143_.py | 173 ------------- backend/migrations/versions/4e5d9f481f22_.py | 28 --- backend/migrations/versions/722b4e7f7a58_.py | 35 --- backend/migrations/versions/7c7cecfe5e6c_.py | 26 -- backend/migrations/versions/86d300cb6d69_.py | 40 --- backend/migrations/versions/9ad68ecf85aa_.py | 30 --- backend/migrations/versions/c0646a888d4f_.py | 28 --- backend/migrations/versions/d39bb526eef4_.py | 51 ---- backend/migrations/versions/e0d970ed6500_.py | 33 --- backend/migrations/versions/ebccb1298297_.py | 28 --- backend/migrations/versions/eddbe541cff1_.py | 29 --- backend/migrations/versions/edf057ef742a_.py | 45 ---- backend/migrations/versions/fa1fedf4ca08_.py | 38 --- 22 files changed, 247 insertions(+), 843 deletions(-) delete mode 100644 backend/migrations/versions/02acd43b4357_.py create mode 100644 backend/migrations/versions/0f08974b4118_.py delete mode 100644 backend/migrations/versions/13365ffe910e_.py delete mode 100644 backend/migrations/versions/27975c4a04a4_.py delete mode 100644 backend/migrations/versions/310dca400b81_.py delete mode 100644 backend/migrations/versions/332a15eba9d8_.py delete mode 100644 backend/migrations/versions/3514aaf4648f_.py delete mode 100644 backend/migrations/versions/3793d9a71e27_.py delete mode 100644 backend/migrations/versions/484abe10e157_.py delete mode 100644 backend/migrations/versions/4af29f8b2143_.py delete mode 100644 backend/migrations/versions/4e5d9f481f22_.py delete mode 100644 backend/migrations/versions/722b4e7f7a58_.py delete mode 100644 backend/migrations/versions/7c7cecfe5e6c_.py delete mode 100644 backend/migrations/versions/86d300cb6d69_.py delete mode 100644 backend/migrations/versions/9ad68ecf85aa_.py delete mode 100644 backend/migrations/versions/c0646a888d4f_.py delete mode 100644 backend/migrations/versions/d39bb526eef4_.py delete mode 100644 backend/migrations/versions/e0d970ed6500_.py delete mode 100644 backend/migrations/versions/ebccb1298297_.py delete mode 100644 backend/migrations/versions/eddbe541cff1_.py delete mode 100644 backend/migrations/versions/edf057ef742a_.py delete mode 100644 backend/migrations/versions/fa1fedf4ca08_.py diff --git a/backend/migrations/versions/02acd43b4357_.py b/backend/migrations/versions/02acd43b4357_.py deleted file mode 100644 index e80162fa..00000000 --- a/backend/migrations/versions/02acd43b4357_.py +++ /dev/null @@ -1,30 +0,0 @@ -"""basic comment table moderation fields - hidden, reported - -Revision ID: 02acd43b4357 -Revises: 27975c4a04a4 -Create Date: 2019-02-17 17:17:17.677275 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '02acd43b4357' -down_revision = '27975c4a04a4' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('comment', sa.Column('hidden', sa.Boolean(), server_default=sa.text('FALSE'), nullable=False)) - op.add_column('comment', sa.Column('reported', sa.Boolean(), server_default=sa.text('FALSE'), nullable=True)) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_column('comment', 'reported') - op.drop_column('comment', 'hidden') - # ### end Alembic commands ### diff --git a/backend/migrations/versions/0f08974b4118_.py b/backend/migrations/versions/0f08974b4118_.py new file mode 100644 index 00000000..ee933d77 --- /dev/null +++ b/backend/migrations/versions/0f08974b4118_.py @@ -0,0 +1,247 @@ +"""empty message + +Revision ID: 0f08974b4118 +Revises: +Create Date: 2019-03-14 14:38:40.894594 + +""" +from alembic import op +import sqlalchemy as sa +from grant.task.models import JsonEncodedDict + +# revision identifiers, used by Alembic. +revision = '0f08974b4118' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade(): +# ### commands auto generated by Alembic - please adjust! ### + op.create_table('rfp', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('date_created', sa.DateTime(), nullable=True), + sa.Column('title', sa.String(length=255), nullable=False), + sa.Column('brief', sa.String(length=255), nullable=False), + sa.Column('content', sa.Text(), nullable=False), + sa.Column('category', sa.String(length=255), nullable=False), + sa.Column('status', sa.String(length=255), nullable=False), + sa.Column('matching', sa.Boolean(), nullable=False), + sa.Column('bounty', sa.String(length=255), nullable=True), + sa.Column('date_closes', sa.DateTime(), nullable=True), + sa.Column('date_opened', sa.DateTime(), nullable=True), + sa.Column('date_closed', sa.DateTime(), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('role', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('name', sa.String(length=80), nullable=True), + sa.Column('description', sa.String(length=255), nullable=True), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('name') + ) + op.create_table('task', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('job_type', sa.Integer(), nullable=False), + sa.Column('blob', JsonEncodedDict(), nullable=False), + sa.Column('execute_after', sa.DateTime(), nullable=False), + sa.Column('completed', sa.Boolean(), nullable=True), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('user', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('email_address', sa.String(length=255), nullable=False), + sa.Column('password', sa.String(length=255), nullable=False), + sa.Column('display_name', sa.String(length=255), nullable=True), + sa.Column('title', sa.String(length=255), nullable=True), + sa.Column('active', sa.Boolean(), nullable=True), + sa.Column('is_admin', sa.Boolean(), server_default=sa.text('FALSE'), nullable=False), + sa.Column('totp_secret', sa.String(length=255), nullable=True), + sa.Column('backup_codes', sa.String(), nullable=True), + sa.Column('silenced', sa.Boolean(), nullable=True), + sa.Column('banned', sa.Boolean(), nullable=True), + sa.Column('banned_reason', sa.String(), nullable=True), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('email_address') + ) + op.create_table('avatar', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('image_url', sa.String(length=255), nullable=True), + sa.Column('user_id', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('email_recovery', + sa.Column('user_id', sa.Integer(), nullable=False), + sa.Column('code', sa.String(length=255), nullable=False), + sa.Column('date_created', sa.DateTime(), nullable=True), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('user_id'), + sa.UniqueConstraint('code') + ) + op.create_table('email_verification', + sa.Column('user_id', sa.Integer(), nullable=False), + sa.Column('code', sa.String(length=255), nullable=False), + sa.Column('has_verified', sa.Boolean(), nullable=True), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('user_id'), + sa.UniqueConstraint('code') + ) + op.create_table('proposal', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('date_created', sa.DateTime(), nullable=True), + sa.Column('rfp_id', sa.Integer(), nullable=True), + sa.Column('status', sa.String(length=255), nullable=False), + sa.Column('title', sa.String(length=255), nullable=False), + sa.Column('brief', sa.String(length=255), nullable=False), + sa.Column('stage', sa.String(length=255), nullable=False), + sa.Column('content', sa.Text(), nullable=False), + sa.Column('category', sa.String(length=255), nullable=False), + sa.Column('date_approved', sa.DateTime(), nullable=True), + sa.Column('date_published', sa.DateTime(), nullable=True), + sa.Column('reject_reason', sa.String(), nullable=True), + sa.Column('target', sa.String(length=255), nullable=False), + sa.Column('payout_address', sa.String(length=255), nullable=False), + sa.Column('deadline_duration', sa.Integer(), nullable=False), + sa.Column('contribution_matching', sa.Float(), server_default=sa.text('0'), nullable=False), + sa.Column('contribution_bounty', sa.String(length=255), server_default=sa.text("'0'"), nullable=False), + sa.Column('rfp_opt_in', sa.Boolean(), nullable=True), + sa.ForeignKeyConstraint(['rfp_id'], ['rfp.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('roles_users', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('user_id', sa.Integer(), nullable=True), + sa.Column('role_id', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['role_id'], ['role.id'], ), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('social_media', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('service', sa.String(length=255), nullable=False), + sa.Column('username', sa.String(length=255), nullable=False), + sa.Column('user_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('user_settings', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('user_id', sa.Integer(), nullable=False), + sa.Column('email_subscriptions', sa.Integer(), nullable=True), + sa.Column('refund_address', sa.String(length=255), nullable=True), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('comment', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('date_created', sa.DateTime(), nullable=True), + sa.Column('content', sa.Text(), nullable=False), + sa.Column('hidden', sa.Boolean(), server_default=sa.text('FALSE'), nullable=False), + sa.Column('reported', sa.Boolean(), server_default=sa.text('FALSE'), nullable=True), + sa.Column('parent_comment_id', sa.Integer(), nullable=True), + sa.Column('proposal_id', sa.Integer(), nullable=False), + sa.Column('user_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['parent_comment_id'], ['comment.id'], ), + sa.ForeignKeyConstraint(['proposal_id'], ['proposal.id'], ), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('milestone', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('index', sa.Integer(), nullable=False), + sa.Column('date_created', sa.DateTime(), nullable=False), + sa.Column('title', sa.String(length=255), nullable=False), + sa.Column('content', sa.Text(), nullable=False), + sa.Column('payout_percent', sa.String(length=255), nullable=False), + sa.Column('immediate_payout', sa.Boolean(), nullable=True), + sa.Column('date_estimated', sa.DateTime(), nullable=False), + sa.Column('stage', sa.String(length=255), nullable=False), + sa.Column('date_requested', sa.DateTime(), nullable=True), + sa.Column('requested_user_id', sa.Integer(), nullable=True), + sa.Column('date_rejected', sa.DateTime(), nullable=True), + sa.Column('reject_reason', sa.String(length=255), nullable=True), + sa.Column('reject_arbiter_id', sa.Integer(), nullable=True), + sa.Column('date_accepted', sa.DateTime(), nullable=True), + sa.Column('accept_arbiter_id', sa.Integer(), nullable=True), + sa.Column('date_paid', sa.DateTime(), nullable=True), + sa.Column('paid_tx_id', sa.String(length=255), nullable=True), + sa.Column('proposal_id', sa.Integer(), nullable=False), + sa.ForeignKeyConstraint(['accept_arbiter_id'], ['user.id'], ), + sa.ForeignKeyConstraint(['proposal_id'], ['proposal.id'], ), + sa.ForeignKeyConstraint(['reject_arbiter_id'], ['user.id'], ), + sa.ForeignKeyConstraint(['requested_user_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('proposal_arbiter', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('proposal_id', sa.Integer(), nullable=False), + sa.Column('user_id', sa.Integer(), nullable=True), + sa.Column('status', sa.String(length=255), nullable=False), + sa.ForeignKeyConstraint(['proposal_id'], ['proposal.id'], ), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('proposal_contribution', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('date_created', sa.DateTime(), nullable=False), + sa.Column('proposal_id', sa.Integer(), nullable=False), + sa.Column('user_id', sa.Integer(), nullable=True), + sa.Column('status', sa.String(length=255), nullable=False), + sa.Column('amount', sa.String(length=255), nullable=False), + sa.Column('tx_id', sa.String(length=255), nullable=True), + sa.Column('refund_tx_id', sa.String(length=255), nullable=True), + sa.Column('staking', sa.Boolean(), nullable=False), + sa.Column('no_refund', sa.Boolean(), nullable=False), + sa.ForeignKeyConstraint(['proposal_id'], ['proposal.id'], ), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('proposal_team', + sa.Column('user_id', sa.Integer(), nullable=True), + sa.Column('proposal_id', sa.Integer(), nullable=True), + sa.ForeignKeyConstraint(['proposal_id'], ['proposal.id'], ), + sa.ForeignKeyConstraint(['user_id'], ['user.id'], ) + ) + op.create_table('proposal_team_invite', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('date_created', sa.DateTime(), nullable=True), + sa.Column('proposal_id', sa.Integer(), nullable=False), + sa.Column('address', sa.String(length=255), nullable=False), + sa.Column('accepted', sa.Boolean(), nullable=True), + sa.ForeignKeyConstraint(['proposal_id'], ['proposal.id'], ), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('proposal_update', + sa.Column('id', sa.Integer(), nullable=False), + sa.Column('date_created', sa.DateTime(), nullable=True), + sa.Column('proposal_id', sa.Integer(), nullable=False), + sa.Column('title', sa.String(length=255), nullable=False), + sa.Column('content', sa.Text(), nullable=False), + sa.ForeignKeyConstraint(['proposal_id'], ['proposal.id'], ), + sa.PrimaryKeyConstraint('id') + ) + # ### end Alembic commands ### + + +def downgrade(): +# ### commands auto generated by Alembic - please adjust! ### + op.drop_table('proposal_update') + op.drop_table('proposal_team_invite') + op.drop_table('proposal_team') + op.drop_table('proposal_contribution') + op.drop_table('proposal_arbiter') + op.drop_table('milestone') + op.drop_table('comment') + op.drop_table('user_settings') + op.drop_table('social_media') + op.drop_table('roles_users') + op.drop_table('proposal') + op.drop_table('email_verification') + op.drop_table('email_recovery') + op.drop_table('avatar') + op.drop_table('user') + op.drop_table('task') + op.drop_table('role') + op.drop_table('rfp') + # ### end Alembic commands ### diff --git a/backend/migrations/versions/13365ffe910e_.py b/backend/migrations/versions/13365ffe910e_.py deleted file mode 100644 index 3a031b05..00000000 --- a/backend/migrations/versions/13365ffe910e_.py +++ /dev/null @@ -1,31 +0,0 @@ -"""add proposal contribution_bounty & rfp_opt_in - -Revision ID: 13365ffe910e -Revises: 332a15eba9d8 -Create Date: 2019-02-28 19:41:42.215923 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '13365ffe910e' -down_revision = '332a15eba9d8' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('proposal', sa.Column('contribution_bounty', sa.String( - length=255), server_default=sa.text("'0'"), nullable=False)) - op.add_column('proposal', sa.Column('rfp_opt_in', sa.Boolean(), nullable=True)) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_column('proposal', 'rfp_opt_in') - op.drop_column('proposal', 'contribution_bounty') - # ### end Alembic commands ### diff --git a/backend/migrations/versions/27975c4a04a4_.py b/backend/migrations/versions/27975c4a04a4_.py deleted file mode 100644 index 24a27e40..00000000 --- a/backend/migrations/versions/27975c4a04a4_.py +++ /dev/null @@ -1,32 +0,0 @@ -"""user banned & silenced fields - -Revision ID: 27975c4a04a4 -Revises: d39bb526eef4 -Create Date: 2019-02-14 10:30:47.596818 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '27975c4a04a4' -down_revision = 'd39bb526eef4' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('user', sa.Column('banned', sa.Boolean(), nullable=True)) - op.add_column('user', sa.Column('banned_reason', sa.String(), nullable=True)) - op.add_column('user', sa.Column('silenced', sa.Boolean(), nullable=True)) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_column('user', 'silenced') - op.drop_column('user', 'banned_reason') - op.drop_column('user', 'banned') - # ### end Alembic commands ### diff --git a/backend/migrations/versions/310dca400b81_.py b/backend/migrations/versions/310dca400b81_.py deleted file mode 100644 index 6c6d4ad1..00000000 --- a/backend/migrations/versions/310dca400b81_.py +++ /dev/null @@ -1,30 +0,0 @@ -"""empty message - -Revision ID: 310dca400b81 -Revises: fa1fedf4ca08 -Create Date: 2019-02-05 11:24:11.291158 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '310dca400b81' -down_revision = 'fa1fedf4ca08' -branch_labels = None -depends_on = None - - -def upgrade(): -# ### commands auto generated by Alembic - please adjust! ### - op.add_column('proposal', sa.Column('arbiter_id', sa.Integer(), nullable=True)) - op.create_foreign_key(None, 'proposal', 'user', ['arbiter_id'], ['id']) - # ### end Alembic commands ### - - -def downgrade(): -# ### commands auto generated by Alembic - please adjust! ### - op.drop_constraint(None, 'proposal', type_='foreignkey') - op.drop_column('proposal', 'arbiter_id') - # ### end Alembic commands ### diff --git a/backend/migrations/versions/332a15eba9d8_.py b/backend/migrations/versions/332a15eba9d8_.py deleted file mode 100644 index e347f4da..00000000 --- a/backend/migrations/versions/332a15eba9d8_.py +++ /dev/null @@ -1,26 +0,0 @@ -"""remove linkedin social_media items - -Revision ID: 332a15eba9d8 -Revises: 7c7cecfe5e6c -Create Date: 2019-02-23 19:51:16.284007 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '332a15eba9d8' -down_revision = '7c7cecfe5e6c' -branch_labels = None -depends_on = None - - -def upgrade(): - connection = op.get_bind() - connection.execute("DELETE FROM social_media WHERE service = 'LINKEDIN'") - - -def downgrade(): - # there is no going back, all your precious linkedin profiles are gone now - pass diff --git a/backend/migrations/versions/3514aaf4648f_.py b/backend/migrations/versions/3514aaf4648f_.py deleted file mode 100644 index b5fcca13..00000000 --- a/backend/migrations/versions/3514aaf4648f_.py +++ /dev/null @@ -1,29 +0,0 @@ -"""Add staking boolean column to proposal_contribution - -Revision ID: 3514aaf4648f -Revises: c0646a888d4f -Create Date: 2019-02-21 12:24:15.800835 - -""" -from alembic import op -import sqlalchemy as sa -from sqlalchemy.sql import expression - - -# revision identifiers, used by Alembic. -revision = '3514aaf4648f' -down_revision = 'c0646a888d4f' -branch_labels = None -depends_on = None - - -def upgrade(): -# ### commands auto generated by Alembic - please adjust! ### - op.add_column('proposal_contribution', sa.Column('staking', sa.Boolean(), nullable=False, server_default=expression.false())) - # ### end Alembic commands ### - - -def downgrade(): -# ### commands auto generated by Alembic - please adjust! ### - op.drop_column('proposal_contribution', 'staking') - # ### end Alembic commands ### diff --git a/backend/migrations/versions/3793d9a71e27_.py b/backend/migrations/versions/3793d9a71e27_.py deleted file mode 100644 index 1dc4bfce..00000000 --- a/backend/migrations/versions/3793d9a71e27_.py +++ /dev/null @@ -1,52 +0,0 @@ -"""milestone payment fields - -Revision ID: 3793d9a71e27 -Revises: 86d300cb6d69 -Create Date: 2019-02-11 11:01:44.703413 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '3793d9a71e27' -down_revision = '86d300cb6d69' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('milestone', sa.Column('accept_arbiter_id', sa.Integer(), nullable=True)) - op.add_column('milestone', sa.Column('date_accepted', sa.DateTime(), nullable=True)) - op.add_column('milestone', sa.Column('date_paid', sa.DateTime(), nullable=True)) - op.add_column('milestone', sa.Column('date_rejected', sa.DateTime(), nullable=True)) - op.add_column('milestone', sa.Column('date_requested', sa.DateTime(), nullable=True)) - op.add_column('milestone', sa.Column('index', sa.Integer(), nullable=False)) - op.add_column('milestone', sa.Column('paid_tx_id', sa.String(length=255), nullable=True)) - op.add_column('milestone', sa.Column('reject_arbiter_id', sa.Integer(), nullable=True)) - op.add_column('milestone', sa.Column('reject_reason', sa.String(length=255), nullable=True)) - op.add_column('milestone', sa.Column('requested_user_id', sa.Integer(), nullable=True)) - op.create_foreign_key(None, 'milestone', 'user', ['accept_arbiter_id'], ['id']) - op.create_foreign_key(None, 'milestone', 'user', ['reject_arbiter_id'], ['id']) - op.create_foreign_key(None, 'milestone', 'user', ['requested_user_id'], ['id']) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_constraint(None, 'milestone', type_='foreignkey') - op.drop_constraint(None, 'milestone', type_='foreignkey') - op.drop_constraint(None, 'milestone', type_='foreignkey') - op.drop_column('milestone', 'requested_user_id') - op.drop_column('milestone', 'reject_reason') - op.drop_column('milestone', 'reject_arbiter_id') - op.drop_column('milestone', 'paid_tx_id') - op.drop_column('milestone', 'index') - op.drop_column('milestone', 'date_requested') - op.drop_column('milestone', 'date_rejected') - op.drop_column('milestone', 'date_paid') - op.drop_column('milestone', 'date_accepted') - op.drop_column('milestone', 'accept_arbiter_id') - # ### end Alembic commands ### diff --git a/backend/migrations/versions/484abe10e157_.py b/backend/migrations/versions/484abe10e157_.py deleted file mode 100644 index b640c805..00000000 --- a/backend/migrations/versions/484abe10e157_.py +++ /dev/null @@ -1,29 +0,0 @@ -"""Add no_refund column to contributions, default to false - -Revision ID: 484abe10e157 -Revises: 13365ffe910e -Create Date: 2019-03-13 16:35:01.520404 - -""" -from alembic import op -import sqlalchemy as sa -from sqlalchemy.sql import expression - - -# revision identifiers, used by Alembic. -revision = '484abe10e157' -down_revision = '13365ffe910e' -branch_labels = None -depends_on = None - - -def upgrade(): -# ### commands auto generated by Alembic - please adjust! ### - op.add_column('proposal_contribution', sa.Column('no_refund', sa.Boolean(), nullable=False, server_default=expression.false())) - # ### end Alembic commands ### - - -def downgrade(): -# ### commands auto generated by Alembic - please adjust! ### - op.drop_column('proposal_contribution', 'no_refund') - # ### end Alembic commands ### diff --git a/backend/migrations/versions/4af29f8b2143_.py b/backend/migrations/versions/4af29f8b2143_.py deleted file mode 100644 index 391272a9..00000000 --- a/backend/migrations/versions/4af29f8b2143_.py +++ /dev/null @@ -1,173 +0,0 @@ -"""empty message - -Revision ID: 4af29f8b2143 -Revises: -Create Date: 2019-01-09 16:35:34.349666 - -""" -import sqlalchemy as sa -from alembic import op - -# revision identifiers, used by Alembic. -revision = '4af29f8b2143' -down_revision = None -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.create_table('proposal', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('date_created', sa.DateTime(), nullable=True), - sa.Column('status', sa.String(length=255), nullable=False), - sa.Column('title', sa.String(length=255), nullable=False), - sa.Column('brief', sa.String(length=255), nullable=False), - sa.Column('stage', sa.String(length=255), nullable=False), - sa.Column('content', sa.Text(), nullable=False), - sa.Column('category', sa.String(length=255), nullable=False), - sa.Column('date_approved', sa.DateTime(), nullable=True), - sa.Column('date_published', sa.DateTime(), nullable=True), - sa.Column('reject_reason', sa.String(), nullable=True), - sa.Column('target', sa.String(length=255), nullable=False), - sa.Column('payout_address', sa.String(length=255), nullable=False), - sa.Column('deadline_duration', sa.Integer(), nullable=False), - sa.PrimaryKeyConstraint('id') - ) - op.create_table('role', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('name', sa.String(length=80), nullable=True), - sa.Column('description', sa.String(length=255), nullable=True), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('name') - ) - op.create_table('user', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('email_address', sa.String(length=255), nullable=False), - sa.Column('password', sa.String(length=255), nullable=False), - sa.Column('display_name', sa.String(length=255), nullable=True), - sa.Column('title', sa.String(length=255), nullable=True), - sa.Column('active', sa.Boolean(), nullable=True), - sa.PrimaryKeyConstraint('id'), - sa.UniqueConstraint('email_address') - ) - op.create_table('avatar', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('image_url', sa.String(length=255), nullable=True), - sa.Column('user_id', sa.Integer(), nullable=True), - sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), - sa.PrimaryKeyConstraint('id') - ) - op.create_table('comment', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('date_created', sa.DateTime(), nullable=True), - sa.Column('content', sa.Text(), nullable=False), - sa.Column('parent_comment_id', sa.Integer(), nullable=True), - sa.Column('proposal_id', sa.Integer(), nullable=False), - sa.Column('user_id', sa.Integer(), nullable=False), - sa.ForeignKeyConstraint(['parent_comment_id'], ['comment.id'], ), - sa.ForeignKeyConstraint(['proposal_id'], ['proposal.id'], ), - sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), - sa.PrimaryKeyConstraint('id') - ) - op.create_table('email_recovery', - sa.Column('user_id', sa.Integer(), nullable=False), - sa.Column('code', sa.String(length=255), nullable=False), - sa.Column('date_created', sa.DateTime(), nullable=True), - sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), - sa.PrimaryKeyConstraint('user_id'), - sa.UniqueConstraint('code') - ) - op.create_table('email_verification', - sa.Column('user_id', sa.Integer(), nullable=False), - sa.Column('code', sa.String(length=255), nullable=False), - sa.Column('has_verified', sa.Boolean(), nullable=True), - sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), - sa.PrimaryKeyConstraint('user_id'), - sa.UniqueConstraint('code') - ) - op.create_table('milestone', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('date_created', sa.DateTime(), nullable=False), - sa.Column('title', sa.String(length=255), nullable=False), - sa.Column('content', sa.Text(), nullable=False), - sa.Column('stage', sa.String(length=255), nullable=False), - sa.Column('payout_percent', sa.String(length=255), nullable=False), - sa.Column('immediate_payout', sa.Boolean(), nullable=True), - sa.Column('date_estimated', sa.DateTime(), nullable=False), - sa.Column('proposal_id', sa.Integer(), nullable=False), - sa.ForeignKeyConstraint(['proposal_id'], ['proposal.id'], ), - sa.PrimaryKeyConstraint('id') - ) - op.create_table('proposal_contribution', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('date_created', sa.DateTime(), nullable=False), - sa.Column('proposal_id', sa.Integer(), nullable=False), - sa.Column('user_id', sa.Integer(), nullable=True), - sa.Column('status', sa.String(length=255), nullable=False), - sa.Column('amount', sa.String(length=255), nullable=False), - sa.Column('tx_id', sa.String(length=255), nullable=True), - sa.ForeignKeyConstraint(['proposal_id'], ['proposal.id'], ), - sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), - sa.PrimaryKeyConstraint('id') - ) - op.create_table('proposal_team', - sa.Column('user_id', sa.Integer(), nullable=True), - sa.Column('proposal_id', sa.Integer(), nullable=True), - sa.ForeignKeyConstraint(['proposal_id'], ['proposal.id'], ), - sa.ForeignKeyConstraint(['user_id'], ['user.id'], ) - ) - op.create_table('proposal_team_invite', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('date_created', sa.DateTime(), nullable=True), - sa.Column('proposal_id', sa.Integer(), nullable=False), - sa.Column('address', sa.String(length=255), nullable=False), - sa.Column('accepted', sa.Boolean(), nullable=True), - sa.ForeignKeyConstraint(['proposal_id'], ['proposal.id'], ), - sa.PrimaryKeyConstraint('id') - ) - op.create_table('proposal_update', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('date_created', sa.DateTime(), nullable=True), - sa.Column('proposal_id', sa.Integer(), nullable=False), - sa.Column('title', sa.String(length=255), nullable=False), - sa.Column('content', sa.Text(), nullable=False), - sa.ForeignKeyConstraint(['proposal_id'], ['proposal.id'], ), - sa.PrimaryKeyConstraint('id') - ) - op.create_table('roles_users', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('user_id', sa.Integer(), nullable=True), - sa.Column('role_id', sa.Integer(), nullable=True), - sa.ForeignKeyConstraint(['role_id'], ['role.id'], ), - sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), - sa.PrimaryKeyConstraint('id') - ) - op.create_table('social_media', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('service', sa.String(length=255), nullable=False), - sa.Column('username', sa.String(length=255), nullable=False), - sa.Column('user_id', sa.Integer(), nullable=False), - sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), - sa.PrimaryKeyConstraint('id') - ) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table('social_media') - op.drop_table('roles_users') - op.drop_table('proposal_update') - op.drop_table('proposal_team_invite') - op.drop_table('proposal_team') - op.drop_table('proposal_contribution') - op.drop_table('milestone') - op.drop_table('email_verification') - op.drop_table('email_recovery') - op.drop_table('comment') - op.drop_table('avatar') - op.drop_table('user') - op.drop_table('role') - op.drop_table('proposal') - # ### end Alembic commands ### diff --git a/backend/migrations/versions/4e5d9f481f22_.py b/backend/migrations/versions/4e5d9f481f22_.py deleted file mode 100644 index 7a91f734..00000000 --- a/backend/migrations/versions/4e5d9f481f22_.py +++ /dev/null @@ -1,28 +0,0 @@ -"""user is_admin field - -Revision ID: 4e5d9f481f22 -Revises: 3514aaf4648f -Create Date: 2019-02-20 11:30:30.376869 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '4e5d9f481f22' -down_revision = '3514aaf4648f' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('user', sa.Column('is_admin', sa.Boolean(), server_default=sa.text('FALSE'), nullable=False)) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_column('user', 'is_admin') - # ### end Alembic commands ### diff --git a/backend/migrations/versions/722b4e7f7a58_.py b/backend/migrations/versions/722b4e7f7a58_.py deleted file mode 100644 index c75b7553..00000000 --- a/backend/migrations/versions/722b4e7f7a58_.py +++ /dev/null @@ -1,35 +0,0 @@ -"""empty message - -Revision ID: 722b4e7f7a58 -Revises: e0d970ed6500 -Create Date: 2019-01-28 20:46:13.497530 - -""" -from alembic import op -import sqlalchemy as sa -from grant.task.models import JsonEncodedDict - -# revision identifiers, used by Alembic. -revision = '722b4e7f7a58' -down_revision = 'e0d970ed6500' -branch_labels = None -depends_on = None - - -def upgrade(): -# ### commands auto generated by Alembic - please adjust! ### - op.create_table('task', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('job_type', sa.Integer(), nullable=False), - sa.Column('blob', JsonEncodedDict(), nullable=False), - sa.Column('execute_after', sa.DateTime(), nullable=False), - sa.Column('completed', sa.Boolean(), nullable=True), - sa.PrimaryKeyConstraint('id') - ) - # ### end Alembic commands ### - - -def downgrade(): -# ### commands auto generated by Alembic - please adjust! ### - op.drop_table('task') - # ### end Alembic commands ### diff --git a/backend/migrations/versions/7c7cecfe5e6c_.py b/backend/migrations/versions/7c7cecfe5e6c_.py deleted file mode 100644 index 33b9aebe..00000000 --- a/backend/migrations/versions/7c7cecfe5e6c_.py +++ /dev/null @@ -1,26 +0,0 @@ -"""Convert REFUNDING stage to FAILED - -Revision ID: 7c7cecfe5e6c -Revises: 9ad68ecf85aa -Create Date: 2019-02-22 13:15:44.997884 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '7c7cecfe5e6c' -down_revision = '9ad68ecf85aa' -branch_labels = None -depends_on = None - - -def upgrade(): - connection = op.get_bind() - connection.execute("UPDATE proposal SET stage = 'FAILED' WHERE stage = 'REFUNDING'") - - -def downgrade(): - connection = op.get_bind() - connection.execute("UPDATE proposal SET stage = 'REFUNDING' WHERE stage = 'FAILED'") \ No newline at end of file diff --git a/backend/migrations/versions/86d300cb6d69_.py b/backend/migrations/versions/86d300cb6d69_.py deleted file mode 100644 index cfea18ef..00000000 --- a/backend/migrations/versions/86d300cb6d69_.py +++ /dev/null @@ -1,40 +0,0 @@ -"""proposal_arbiter table - -Revision ID: 86d300cb6d69 -Revises: 310dca400b81 -Create Date: 2019-02-08 13:06:39.201691 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '86d300cb6d69' -down_revision = '310dca400b81' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.create_table('proposal_arbiter', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('proposal_id', sa.Integer(), nullable=False), - sa.Column('user_id', sa.Integer(), nullable=True), - sa.Column('status', sa.String(length=255), nullable=False), - sa.ForeignKeyConstraint(['proposal_id'], ['proposal.id'], ), - sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), - sa.PrimaryKeyConstraint('id') - ) - op.drop_constraint('proposal_arbiter_id_fkey', 'proposal', type_='foreignkey') - op.drop_column('proposal', 'arbiter_id') - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('proposal', sa.Column('arbiter_id', sa.INTEGER(), autoincrement=False, nullable=True)) - op.create_foreign_key('proposal_arbiter_id_fkey', 'proposal', 'user', ['arbiter_id'], ['id']) - op.drop_table('proposal_arbiter') - # ### end Alembic commands ### diff --git a/backend/migrations/versions/9ad68ecf85aa_.py b/backend/migrations/versions/9ad68ecf85aa_.py deleted file mode 100644 index 750116cc..00000000 --- a/backend/migrations/versions/9ad68ecf85aa_.py +++ /dev/null @@ -1,30 +0,0 @@ -"""2fa user fields: backup_codes & totp_secret - -Revision ID: 9ad68ecf85aa -Revises: 4e5d9f481f22 -Create Date: 2019-02-21 13:26:32.715454 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '9ad68ecf85aa' -down_revision = '4e5d9f481f22' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('user', sa.Column('backup_codes', sa.String(), nullable=True)) - op.add_column('user', sa.Column('totp_secret', sa.String(length=255), nullable=True)) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_column('user', 'totp_secret') - op.drop_column('user', 'backup_codes') - # ### end Alembic commands ### diff --git a/backend/migrations/versions/c0646a888d4f_.py b/backend/migrations/versions/c0646a888d4f_.py deleted file mode 100644 index 449b2e6f..00000000 --- a/backend/migrations/versions/c0646a888d4f_.py +++ /dev/null @@ -1,28 +0,0 @@ -"""Add refund_tx_id to proposal_contributions - -Revision ID: c0646a888d4f -Revises: ebccb1298297 -Create Date: 2019-02-17 11:36:45.851391 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = 'c0646a888d4f' -down_revision = 'ebccb1298297' -branch_labels = None -depends_on = None - - -def upgrade(): -# ### commands auto generated by Alembic - please adjust! ### - op.add_column('proposal_contribution', sa.Column('refund_tx_id', sa.String(length=255), nullable=True)) - # ### end Alembic commands ### - - -def downgrade(): -# ### commands auto generated by Alembic - please adjust! ### - op.drop_column('proposal_contribution', 'refund_tx_id') - # ### end Alembic commands ### diff --git a/backend/migrations/versions/d39bb526eef4_.py b/backend/migrations/versions/d39bb526eef4_.py deleted file mode 100644 index fecd583e..00000000 --- a/backend/migrations/versions/d39bb526eef4_.py +++ /dev/null @@ -1,51 +0,0 @@ -"""Adds RFP bounty, matching, and date fields - -Revision ID: d39bb526eef4 -Revises: 3793d9a71e27 -Create Date: 2019-02-07 15:09:11.548655 - -""" -from alembic import op -import sqlalchemy as sa -from sqlalchemy.sql import expression -from datetime import datetime, timedelta - - -# revision identifiers, used by Alembic. -revision = 'd39bb526eef4' -down_revision = '3793d9a71e27' -branch_labels = None -depends_on = None - - -def upgrade(): -# ### commands auto generated by Alembic - please adjust! ### - op.add_column('rfp', sa.Column('bounty', sa.String(length=255), nullable=True)) - op.add_column('rfp', sa.Column('date_closed', sa.DateTime(), nullable=True)) - op.add_column('rfp', sa.Column('date_closes', sa.DateTime(), nullable=True)) - op.add_column('rfp', sa.Column('date_opened', sa.DateTime(), nullable=True)) - op.add_column('rfp', sa.Column('matching', sa.Boolean(), nullable=False, server_default=expression.false())) - # ### end Alembic commands ### - - # Set columns for times based on status. - connection = op.get_bind() - connection.execute("UPDATE rfp SET date_opened = now() - INTERVAL '1 DAY' WHERE status = 'LIVE' OR status = 'CLOSED'") - connection.execute("UPDATE rfp SET date_closed = now() WHERE status = 'CLOSED'") - - - -def downgrade(): -# ### commands auto generated by Alembic - please adjust! ### - op.drop_column('rfp', 'matching') - op.drop_column('rfp', 'date_opened') - op.drop_column('rfp', 'date_closes') - op.drop_column('rfp', 'date_closed') - op.drop_column('rfp', 'bounty') - op.create_table('rfp_proposal', - sa.Column('rfp_id', sa.INTEGER(), autoincrement=False, nullable=True), - sa.Column('proposal_id', sa.INTEGER(), autoincrement=False, nullable=True), - sa.ForeignKeyConstraint(['proposal_id'], ['proposal.id'], name='rfp_proposal_proposal_id_fkey'), - sa.ForeignKeyConstraint(['rfp_id'], ['rfp.id'], name='rfp_proposal_rfp_id_fkey'), - sa.UniqueConstraint('proposal_id', name='rfp_proposal_proposal_id_key') - ) - # ### end Alembic commands ### diff --git a/backend/migrations/versions/e0d970ed6500_.py b/backend/migrations/versions/e0d970ed6500_.py deleted file mode 100644 index c79581fb..00000000 --- a/backend/migrations/versions/e0d970ed6500_.py +++ /dev/null @@ -1,33 +0,0 @@ -"""empty message - -Revision ID: e0d970ed6500 -Revises: 4af29f8b2143 -Create Date: 2019-01-10 14:44:42.536248 - -""" -import sqlalchemy as sa -from alembic import op - -# revision identifiers, used by Alembic. -revision = 'e0d970ed6500' -down_revision = '4af29f8b2143' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.create_table('user_settings', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('email_subscriptions', sa.Integer(), nullable=True), - sa.Column('user_id', sa.Integer(), nullable=False), - sa.ForeignKeyConstraint(['user_id'], ['user.id'], ), - sa.PrimaryKeyConstraint('id') - ) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table('user_settings') - # ### end Alembic commands ### diff --git a/backend/migrations/versions/ebccb1298297_.py b/backend/migrations/versions/ebccb1298297_.py deleted file mode 100644 index 1c888283..00000000 --- a/backend/migrations/versions/ebccb1298297_.py +++ /dev/null @@ -1,28 +0,0 @@ -"""Adds refund_address to user_settings - -Revision ID: ebccb1298297 -Revises: 02acd43b4357 -Create Date: 2019-02-16 11:37:46.900729 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = 'ebccb1298297' -down_revision = '02acd43b4357' -branch_labels = None -depends_on = None - - -def upgrade(): -# ### commands auto generated by Alembic - please adjust! ### - op.add_column('user_settings', sa.Column('refund_address', sa.String(length=255), nullable=True)) - # ### end Alembic commands ### - - -def downgrade(): -# ### commands auto generated by Alembic - please adjust! ### - op.drop_column('user_settings', 'refund_address') - # ### end Alembic commands ### diff --git a/backend/migrations/versions/eddbe541cff1_.py b/backend/migrations/versions/eddbe541cff1_.py deleted file mode 100644 index 115215ba..00000000 --- a/backend/migrations/versions/eddbe541cff1_.py +++ /dev/null @@ -1,29 +0,0 @@ -"""empty message - -Revision ID: eddbe541cff1 -Revises: 722b4e7f7a58 -Create Date: 2019-01-24 11:20:32.989266 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = 'eddbe541cff1' -down_revision = '722b4e7f7a58' -branch_labels = None -depends_on = None - - -def upgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.add_column('proposal', sa.Column('contribution_matching', - sa.Float(), server_default=sa.text('0'), nullable=False)) - # ### end Alembic commands ### - - -def downgrade(): - # ### commands auto generated by Alembic - please adjust! ### - op.drop_column('proposal', 'contribution_matching') - # ### end Alembic commands ### diff --git a/backend/migrations/versions/edf057ef742a_.py b/backend/migrations/versions/edf057ef742a_.py deleted file mode 100644 index 72548bdd..00000000 --- a/backend/migrations/versions/edf057ef742a_.py +++ /dev/null @@ -1,45 +0,0 @@ -"""empty message - -Revision ID: edf057ef742a -Revises: eddbe541cff1 -Create Date: 2019-01-25 14:37:07.858965 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = 'edf057ef742a' -down_revision = 'eddbe541cff1' -branch_labels = None -depends_on = None - - -def upgrade(): -# ### commands auto generated by Alembic - please adjust! ### - op.create_table('rfp', - sa.Column('id', sa.Integer(), nullable=False), - sa.Column('date_created', sa.DateTime(), nullable=True), - sa.Column('title', sa.String(length=255), nullable=False), - sa.Column('brief', sa.String(length=255), nullable=False), - sa.Column('content', sa.Text(), nullable=False), - sa.Column('category', sa.String(length=255), nullable=False), - sa.Column('status', sa.String(length=255), nullable=False), - sa.PrimaryKeyConstraint('id') - ) - op.create_table('rfp_proposal', - sa.Column('rfp_id', sa.Integer(), nullable=True), - sa.Column('proposal_id', sa.Integer(), nullable=True), - sa.ForeignKeyConstraint(['proposal_id'], ['proposal.id'], ), - sa.ForeignKeyConstraint(['rfp_id'], ['rfp.id'], ), - sa.UniqueConstraint('proposal_id') - ) - # ### end Alembic commands ### - - -def downgrade(): -# ### commands auto generated by Alembic - please adjust! ### - op.drop_table('rfp_proposal') - op.drop_table('rfp') - # ### end Alembic commands ### diff --git a/backend/migrations/versions/fa1fedf4ca08_.py b/backend/migrations/versions/fa1fedf4ca08_.py deleted file mode 100644 index f7489174..00000000 --- a/backend/migrations/versions/fa1fedf4ca08_.py +++ /dev/null @@ -1,38 +0,0 @@ -"""empty message - -Revision ID: fa1fedf4ca08 -Revises: edf057ef742a -Create Date: 2019-01-30 19:12:04.385472 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = 'fa1fedf4ca08' -down_revision = 'edf057ef742a' -branch_labels = None -depends_on = None - - -def upgrade(): -# ### commands auto generated by Alembic - please adjust! ### - op.drop_table('rfp_proposal') - op.add_column('proposal', sa.Column('rfp_id', sa.Integer(), nullable=True)) - op.create_foreign_key(None, 'proposal', 'rfp', ['rfp_id'], ['id']) - # ### end Alembic commands ### - - -def downgrade(): -# ### commands auto generated by Alembic - please adjust! ### - op.drop_constraint(None, 'proposal', type_='foreignkey') - op.drop_column('proposal', 'rfp_id') - op.create_table('rfp_proposal', - sa.Column('rfp_id', sa.INTEGER(), autoincrement=False, nullable=True), - sa.Column('proposal_id', sa.INTEGER(), autoincrement=False, nullable=True), - sa.ForeignKeyConstraint(['proposal_id'], ['proposal.id'], name='rfp_proposal_proposal_id_fkey'), - sa.ForeignKeyConstraint(['rfp_id'], ['rfp.id'], name='rfp_proposal_rfp_id_fkey'), - sa.UniqueConstraint('proposal_id', name='rfp_proposal_proposal_id_key') - ) - # ### end Alembic commands ### From 9b542ab1f15226f86b0542935777a4d2294ec3a9 Mon Sep 17 00:00:00 2001 From: Daniel Ternyak Date: Thu, 14 Mar 2019 15:29:02 -0500 Subject: [PATCH 59/72] More webargs validation (#344) --- backend/README.md | 13 ++----- backend/grant/admin/views.py | 18 +++------- backend/grant/app.py | 6 +++- backend/grant/milestone/models.py | 19 +++++++++- backend/grant/proposal/models.py | 55 ++++++++++++++++++++++------ backend/grant/proposal/views.py | 57 ++++++++++++------------------ backend/grant/user/views.py | 35 +++++++++--------- backend/grant/utils/requests.py | 46 +++++++++++++++++------- backend/requirements/prod.txt | 10 ++++-- backend/tests/config.py | 35 ++++++++++++++---- backend/tests/proposal/test_api.py | 49 +++++++++++++++++++++++-- backend/tests/test_data.py | 1 - 12 files changed, 232 insertions(+), 112 deletions(-) diff --git a/backend/README.md b/backend/README.md index ec0d309c..f585f594 100644 --- a/backend/README.md +++ b/backend/README.md @@ -147,7 +147,7 @@ To set a user to admin These instructions are for `development`, for `production` simply replace all hostnames/ips/ports with the proper production hostname. -1. Create Github oauth app https://github.com/settings/developers +1. Create GitHub oauth app https://github.com/settings/developers 1. select tab **OAuth Apps** > click **New OAuth App** button 1. set **Homepage URL** to `http://localhost:3000` @@ -162,13 +162,4 @@ These instructions are for `development`, for `production` simply replace all ho 1. set **Callback URLs** to `http://127.0.0.1:3000/callback/twitter` 1. fill out other required fields 1. after create, select **Keys and tokens** tab - 1. save **Consumer API key** and **Consumer API secret key** to `.env` `TWITTER_CLIENT_ID` & `TWITTER_CLIENT_SECRET` respectively. - -1. Create Linkedin oauth app https://www.linkedin.com/developer/apps/new - 1. set **Website URL** to `http://localhost:3000` - 1. fill out other necessary fields & submit - 1. select the **Authentication** tab in app details - 1. check the **r_basicprofile** box under **Default Application Permissions** - 1. Under **OAuth 2.0** > **Authorized Redirect URLs** add `http://localhost:3000/callback/linkedin` - 1. click **update** button - 1. save **Client ID** and **Client Secret** to `.env` `LINKEDIN_CLIENT_ID` & `LINKEDIN_CLIENT_SECRET` respectively. + 1. save **Consumer API key** and **Consumer API secret key** to `.env` `TWITTER_CLIENT_ID` & `TWITTER_CLIENT_SECRET` respectively. \ No newline at end of file diff --git a/backend/grant/admin/views.py b/backend/grant/admin/views.py index 9e663d9a..a3397c72 100644 --- a/backend/grant/admin/views.py +++ b/backend/grant/admin/views.py @@ -499,13 +499,12 @@ def get_rfp(rfp_id): @body({ "title": fields.Str(required=True), "brief": fields.Str(required=True), + "status": fields.Str(required=True, validate=validate.OneOf(choices=RFPStatus.list())), "content": fields.Str(required=True), - "status": fields.Str(required=True), "category": fields.Str(required=True, validate=validate.OneOf(choices=Category.list())), "bounty": fields.Str(required=False, allow_none=True, missing=None), "matching": fields.Bool(required=False, default=False, missing=False), "dateCloses": fields.Int(required=False, missing=None), - "status": fields.Str(required=True, validate=validate.OneOf(choices=RFPStatus.list())), }) @admin.admin_auth_required def update_rfp(rfp_id, title, brief, content, category, bounty, matching, date_closes, status): @@ -569,8 +568,7 @@ def get_contributions(page, filters, search, sort): @body({ "proposalId": fields.Int(required=True), "userId": fields.Int(required=True), - # TODO guard status - "status": fields.Str(required=True), + "status": fields.Str(required=True, validate=validate.OneOf(choices=RFPStatus.list())), "amount": fields.Str(required=True), "txId": fields.Str(required=False, missing=None) }) @@ -582,7 +580,6 @@ def create_contribution(proposal_id, user_id, status, amount, tx_id): user_id=user_id, amount=amount, ) - # TODO guard status contribution.status = status contribution.tx_id = tx_id @@ -610,8 +607,7 @@ def get_contribution(contribution_id): @body({ "proposalId": fields.Int(required=False, missing=None), "userId": fields.Int(required=False, missing=None), - # TODO guard status - "status": fields.Str(required=False, missing=None), + "status": fields.Str(required=True, validate=validate.OneOf(choices=RFPStatus.list())), "amount": fields.Str(required=False, missing=None), "txId": fields.Str(required=False, missing=None), "refundTxId": fields.Str(required=False, allow_none=True, missing=None), @@ -674,12 +670,7 @@ def edit_contribution(contribution_id, proposal_id, user_id, status, amount, tx_ @blueprint.route('/comments', methods=['GET']) -@body({ - "page": fields.Int(required=False, missing=None), - "filters": fields.List(fields.Str(), required=False, missing=None), - "search": fields.Str(required=False, missing=None), - "sort": fields.Str(required=False, missing=None), -}) +@body(paginated_fields) @admin.admin_auth_required def get_comments(page, filters, search, sort): filters_workaround = request.args.getlist('filters[]') @@ -697,7 +688,6 @@ def get_comments(page, filters, search, sort): @body({ "hidden": fields.Bool(required=False, missing=None), "reported": fields.Bool(required=False, missing=None), - }) @admin.admin_auth_required def edit_comment(comment_id, hidden, reported): diff --git a/backend/grant/app.py b/backend/grant/app.py index 399c1b53..b6f2c9a1 100644 --- a/backend/grant/app.py +++ b/backend/grant/app.py @@ -4,7 +4,7 @@ import sentry_sdk import logging import traceback from animal_case import animalify -from flask import Flask, Response, jsonify, request, g +from flask import Flask, Response, jsonify, request, current_app, g from flask_cors import CORS from flask_security import SQLAlchemyUserDatastore from flask_sslify import SSLify @@ -53,6 +53,10 @@ def create_app(config_objects=["grant.settings"]): if type(messages) == dict: if 'json' in messages: error_message = messages['json'][0] + else: + current_app.logger.warn( + f"Unexpected error occurred: {messages}" + ) if headers: return jsonify({"message": error_message}), err.code, headers else: diff --git a/backend/grant/milestone/models.py b/backend/grant/milestone/models.py index 16bf262c..cab8d261 100644 --- a/backend/grant/milestone/models.py +++ b/backend/grant/milestone/models.py @@ -1,9 +1,9 @@ import datetime from grant.extensions import ma, db +from grant.utils.enums import MilestoneStage from grant.utils.exceptions import ValidationException from grant.utils.ma_fields import UnixDate -from grant.utils.enums import MilestoneStage from grant.utils.misc import gen_random_id @@ -63,6 +63,23 @@ class Milestone(db.Model): self.date_created = datetime.datetime.now() self.index = index + @staticmethod + def make(milestones_data, proposal): + if milestones_data: + # Delete & re-add milestones + [db.session.delete(x) for x in proposal.milestones] + for i, milestone_data in enumerate(milestones_data): + m = Milestone( + title=milestone_data["title"], + content=milestone_data["content"], + date_estimated=datetime.datetime.fromtimestamp(milestone_data["date_estimated"]), + payout_percent=str(milestone_data["payout_percent"]), + immediate_payout=milestone_data["immediate_payout"], + proposal_id=proposal.id, + index=i + ) + db.session.add(m) + @staticmethod def validate(milestone): if len(milestone.title) > 60: diff --git a/backend/grant/proposal/models.py b/backend/grant/proposal/models.py index c435f73c..4cd9de39 100644 --- a/backend/grant/proposal/models.py +++ b/backend/grant/proposal/models.py @@ -1,18 +1,18 @@ import datetime +from decimal import Decimal from functools import reduce + +from flask import current_app +from marshmallow import post_dump from sqlalchemy import func, or_ from sqlalchemy.ext.hybrid import hybrid_property -from decimal import Decimal -from marshmallow import post_dump from flask import current_app from grant.comment.models import Comment from grant.email.send import send_email from grant.extensions import ma, db -from grant.utils.exceptions import ValidationException -from grant.utils.misc import dt_to_unix, make_url, gen_random_id -from grant.utils.requests import blockchain_get from grant.settings import PROPOSAL_STAKING_AMOUNT +from grant.task.jobs import ContributionExpired from grant.utils.enums import ( ProposalStatus, ProposalStage, @@ -21,8 +21,10 @@ from grant.utils.enums import ( ProposalArbiterStatus, MilestoneStage ) +from grant.utils.exceptions import ValidationException +from grant.utils.misc import dt_to_unix, make_url, gen_random_id +from grant.utils.requests import blockchain_get from grant.utils.stubs import anonymous_user -from grant.task.jobs import ContributionExpired proposal_team = db.Table( 'proposal_team', db.Model.metadata, @@ -272,10 +274,11 @@ class Proposal(db.Model): self.stage = stage @staticmethod - def validate(proposal): + def simple_validate(proposal): title = proposal.get('title') stage = proposal.get('stage') category = proposal.get('category') + if title and len(title) > 60: raise ValidationException("Proposal title cannot be longer than 60 characters") if stage and not ProposalStage.includes(stage): @@ -283,8 +286,40 @@ class Proposal(db.Model): if category and not Category.includes(category): raise ValidationException("Category {} not a valid category".format(category)) + def validate_publishable_milestones(self): + payout_total = 0.0 + for i, milestone in enumerate(self.milestones): + + if milestone.immediate_payout and i != 0: + raise ValidationException("Only the first milestone can have an immediate payout") + + if len(milestone.title) > 60: + raise ValidationException("Milestone title must be no more than 60 chars") + + if len(milestone.content) > 200: + raise ValidationException("Milestone content must be no more than 200 chars") + + payout_total += float(milestone.payout_percent) + + try: + present = datetime.datetime.today().replace(day=1) + if present > milestone.date_estimated: + raise ValidationException("Milestone date_estimated must be in the future ") + + except Exception as e: + current_app.logger.warn( + f"Unexpected validation error - client prohibits {e}" + ) + raise ValidationException("date_estimated does not convert to a datetime") + + if payout_total != 100.0: + raise ValidationException("payoutPercent across milestones must sum to exactly 100") + def validate_publishable(self): + self.validate_publishable_milestones() + # Require certain fields + required_fields = ['title', 'content', 'brief', 'category', 'target', 'payout_address'] for field in required_fields: if not hasattr(self, field): @@ -300,11 +335,11 @@ class Proposal(db.Model): raise ValidationException("Payout address is not a valid Zcash address") # Then run through regular validation - Proposal.validate(vars(self)) + Proposal.simple_validate(vars(self)) @staticmethod def create(**kwargs): - Proposal.validate(kwargs) + Proposal.simple_validate(kwargs) proposal = Proposal( **kwargs ) @@ -352,7 +387,7 @@ class Proposal(db.Model): self.target = target self.payout_address = payout_address self.deadline_duration = deadline_duration - Proposal.validate(vars(self)) + Proposal.simple_validate(vars(self)) def update_rfp_opt_in(self, opt_in: bool): self.rfp_opt_in = opt_in diff --git a/backend/grant/proposal/views.py b/backend/grant/proposal/views.py index 354e8369..576f508f 100644 --- a/backend/grant/proposal/views.py +++ b/backend/grant/proposal/views.py @@ -1,4 +1,3 @@ -from datetime import datetime from decimal import Decimal from flask import Blueprint, g, request, current_app @@ -218,11 +217,10 @@ def get_proposal_drafts(): @blueprint.route("/", methods=["PUT"]) @requires_team_member_auth -# TODO add gaurd (minimum, maximum, shape) @body({ "title": fields.Str(required=True), "brief": fields.Str(required=True), - "category": fields.Str(required=True, validate=validate.OneOf(choices=Category.list())), + "category": fields.Str(required=True, validate=validate.OneOf(choices=Category.list() + [''])), "content": fields.Str(required=True), "target": fields.Str(required=True), "payoutAddress": fields.Str(required=True), @@ -233,6 +231,11 @@ def get_proposal_drafts(): def update_proposal(milestones, proposal_id, rfp_opt_in, **kwargs): # Update the base proposal fields try: + if g.current_proposal.status not in [ProposalStatus.DRAFT, + ProposalStatus.REJECTED]: + raise ValidationException( + f"Proposal with status: {g.current_proposal.status} are not authorized for updates" + ) g.current_proposal.update(**kwargs) except ValidationException as e: return {"message": "{}".format(str(e))}, 400 @@ -242,20 +245,7 @@ def update_proposal(milestones, proposal_id, rfp_opt_in, **kwargs): if rfp_opt_in is not None: g.current_proposal.update_rfp_opt_in(rfp_opt_in) - # Delete & re-add milestones - [db.session.delete(x) for x in g.current_proposal.milestones] - if milestones: - for i, mdata in enumerate(milestones): - m = Milestone( - title=mdata["title"], - content=mdata["content"], - date_estimated=datetime.fromtimestamp(mdata["date_estimated"]), - payout_percent=str(mdata["payout_percent"]), - immediate_payout=mdata["immediate_payout"], - proposal_id=g.current_proposal.id, - index=i - ) - db.session.add(m) + Milestone.make(milestones, g.current_proposal) # Commit db.session.commit() @@ -358,8 +348,8 @@ def get_proposal_update(proposal_id, update_id): @limiter.limit("5/day;1/minute") @requires_team_member_auth @body({ - "title": fields.Str(required=True), - "content": fields.Str(required=True) + "title": fields.Str(required=True, validate=lambda p: 3 <= len(p) <= 30), + "content": fields.Str(required=True, validate=lambda p: 5 <= len(p) <= 10000), }) def post_proposal_update(proposal_id, title, content): update = ProposalUpdate( @@ -443,24 +433,24 @@ def get_proposal_contributions(proposal_id): if not proposal: return {"message": "No proposal matching id"}, 404 - top_contributions = ProposalContribution.query \ - .filter_by( + top_contributions = ProposalContribution.query.filter_by( proposal_id=proposal_id, status=ContributionStatus.CONFIRMED, staking=False, - ) \ - .order_by(ProposalContribution.amount.desc()) \ - .limit(5) \ - .all() - latest_contributions = ProposalContribution.query \ - .filter_by( + ).order_by( + ProposalContribution.amount.desc() + ).limit( + 5 + ).all() + latest_contributions = ProposalContribution.query.filter_by( proposal_id=proposal_id, status=ContributionStatus.CONFIRMED, staking=False, - ) \ - .order_by(ProposalContribution.date_created.desc()) \ - .limit(5) \ - .all() + ).order_by( + ProposalContribution.date_created.desc() + ).limit( + 5 + ).all() return { 'top': proposal_proposal_contributions_schema.dump(top_contributions), @@ -483,9 +473,8 @@ def get_proposal_contribution(proposal_id, contribution_id): @blueprint.route("//contributions", methods=["POST"]) @limiter.limit("30/day;10/hour;2/minute") -# TODO add gaurd (minimum, maximum) @body({ - "amount": fields.Str(required=True), + "amount": fields.Str(required=True, validate=lambda p: 0.0001 <= float(p) <= 1000000), "anonymous": fields.Bool(required=False, missing=None), "noRefund": fields.Bool(required=False, missing=False), }) @@ -654,7 +643,7 @@ def accept_milestone_payout_request(proposal_id, milestone_id): @blueprint.route("//milestone//reject", methods=["PUT"]) @requires_arbiter_auth @body({ - "reason": fields.Str(required=True) + "reason": fields.Str(required=True, validate=lambda p: 2 <= len(p) <= 200), }) def reject_milestone_payout_request(proposal_id, milestone_id, reason): if not g.current_proposal.is_funded: diff --git a/backend/grant/user/views.py b/backend/grant/user/views.py index c431e43d..14b90f3a 100644 --- a/backend/grant/user/views.py +++ b/backend/grant/user/views.py @@ -1,15 +1,16 @@ +import validators from animal_case import keys_to_snake_case -from flask import Blueprint, g +from flask import Blueprint, g, current_app from marshmallow import fields +from validate_email import validate_email import grant.utils.auth as auth -from grant.extensions import limiter from grant.comment.models import Comment, user_comments_schema from grant.email.models import EmailRecovery +from grant.extensions import limiter from grant.parser import query, body from grant.proposal.models import ( Proposal, - proposal_team, ProposalTeamInvite, invites_with_proposal_schema, ProposalContribution, @@ -19,6 +20,7 @@ from grant.proposal.models import ( ) from grant.utils.enums import ProposalStatus, ContributionStatus from grant.utils.exceptions import ValidationException +from grant.utils.requests import validate_blockchain_get from grant.utils.social import verify_social, get_social_login_url, VerifySocialException from grant.utils.upload import remove_avatar, sign_avatar_upload, AvatarException from .models import ( @@ -27,7 +29,6 @@ from .models import ( Avatar, self_user_schema, user_schema, - users_schema, user_settings_schema, db ) @@ -92,11 +93,10 @@ def get_user(user_id, with_proposals, with_comments, with_funded, with_pending, @blueprint.route("/", methods=["POST"]) @limiter.limit("30/day;5/minute") @body({ - # TODO guard all (valid, minimum, maximum) - "emailAddress": fields.Str(required=True), + "emailAddress": fields.Str(required=True, validate=lambda e: validate_email(e)), "password": fields.Str(required=True), - "displayName": fields.Str(required=True), - "title": fields.Str(required=True), + "displayName": fields.Str(required=True, validate=lambda p: 2 <= len(p) <= 200), + "title": fields.Str(required=True, validate=lambda p: 2 <= len(p) <= 200), }) def create_user( email_address, @@ -131,7 +131,6 @@ def auth_user(email, password): @blueprint.route("/me/password", methods=["PUT"]) @auth.requires_auth -# TODO gaurd password (minimum) @body({ "currentPassword": fields.Str(required=True), "password": fields.Str(required=True) @@ -145,14 +144,16 @@ def update_user_password(current_password, password): @blueprint.route("/me/email", methods=["PUT"]) @auth.requires_auth -# TODO gaurd all (valid, minimum) @body({ - "email": fields.Str(required=True), + "email": fields.Str(required=True, validate=lambda e: validate_email(e)), "password": fields.Str(required=True) }) def update_user_email(email, password): if not g.current_user.check_password(password): return {"message": "Password is incorrect"}, 403 + current_app.logger.info( + f"Updating userId: {g.current_user.id} with current email: {g.current_user.email_address} to new email: {email}" + ) g.current_user.set_email(email) return {"message": "ok"}, 200 @@ -223,7 +224,6 @@ def recover_user(email): @blueprint.route("/recover/", methods=["POST"]) -# TODO gaurd length @body({ "password": fields.Str(required=True) }) @@ -269,12 +269,11 @@ def delete_avatar(url): @blueprint.route("/", methods=["PUT"]) @auth.requires_auth @auth.requires_same_user_auth -# TODO gaurd all (minimum, minimum, shape, uri) @body({ - "displayName": fields.Str(required=True), - "title": fields.Str(required=True), + "displayName": fields.Str(required=True, validate=lambda d: 2 <= len(d) <= 60), + "title": fields.Str(required=True, validate=lambda t: 2 <= len(t) <= 60), "socialMedias": fields.List(fields.Dict(), required=True), - "avatar": fields.Str(required=True) + "avatar": fields.Str(required=True, validate=lambda d: validators.url(d)) }) def update_user(user_id, display_name, title, social_medias, avatar): user = g.current_user @@ -344,10 +343,10 @@ def get_user_settings(user_id): @blueprint.route("//settings", methods=["PUT"]) @auth.requires_same_user_auth -# TODO guard all (shape, validity) @body({ "emailSubscriptions": fields.Dict(required=False, missing=None), - "refundAddress": fields.Str(required=False, missing=None) + "refundAddress": fields.Str(required=False, missing=None, + validate=lambda r: validate_blockchain_get('/validate/address', {'address': r})) }) def set_user_settings(user_id, email_subscriptions, refund_address): if email_subscriptions: diff --git a/backend/grant/utils/requests.py b/backend/grant/utils/requests.py index 9b18c80e..cd63b52d 100644 --- a/backend/grant/utils/requests.py +++ b/backend/grant/utils/requests.py @@ -1,5 +1,8 @@ import requests +from flask import current_app + from grant.settings import BLOCKCHAIN_REST_API_URL, BLOCKCHAIN_API_SECRET, E2E_TESTING +from grant.utils.exceptions import ValidationException ### REST API ### @@ -14,23 +17,42 @@ def handle_res(res): def blockchain_get(path, params=None): if E2E_TESTING: return blockchain_rest_e2e(path, params) - res = requests.get( - f'{BLOCKCHAIN_REST_API_URL}{path}', - headers={'authorization': BLOCKCHAIN_API_SECRET}, - params=params, - ) - return handle_res(res) + try: + res = requests.get( + f'{BLOCKCHAIN_REST_API_URL}{path}', + headers={'authorization': BLOCKCHAIN_API_SECRET}, + params=params, + ) + return handle_res(res) + except Exception as e: + current_app.logger.error(f"Unable to contact node: {e}") + raise e + + +def validate_blockchain_get(path, params=None): + try: + res = blockchain_get(path, params) + except Exception: + raise ValidationException('Unable to validate zcash address right now, try again later') + if not res.get('valid'): + raise ValidationException('Invalid Zcash address') + + return True def blockchain_post(path, data=None): if E2E_TESTING: return blockchain_rest_e2e(path, data) - res = requests.post( - f'{BLOCKCHAIN_REST_API_URL}{path}', - headers={'authorization': BLOCKCHAIN_API_SECRET}, - json=data, - ) - return handle_res(res) + try: + res = requests.post( + f'{BLOCKCHAIN_REST_API_URL}{path}', + headers={'authorization': BLOCKCHAIN_API_SECRET}, + json=data, + ) + return handle_res(res) + except Exception as e: + current_app.logger.error(f"Unable to contact node: {e}") + raise e def blockchain_rest_e2e(path, data): diff --git a/backend/requirements/prod.txt b/backend/requirements/prod.txt index 39ad3deb..17c84bfd 100644 --- a/backend/requirements/prod.txt +++ b/backend/requirements/prod.txt @@ -69,7 +69,7 @@ Flask-Security==3.0.0 requests-oauthlib==1.0.0 # request parsing -webargs==5.1.2 +webargs==5.1.3 # 2fa - totp pyotp==2.2.7 @@ -78,4 +78,10 @@ pyotp==2.2.7 animal_case==0.4.1 # Rate limiting -Flask-Limiter==1.0.1 \ No newline at end of file +Flask-Limiter==1.0.1 + +# validate email +validate_email==1.3 + +# validate URLS +validators==0.12.4 \ No newline at end of file diff --git a/backend/tests/config.py b/backend/tests/config.py index f1b1688d..bdf196b3 100644 --- a/backend/tests/config.py +++ b/backend/tests/config.py @@ -1,15 +1,18 @@ import json -from mock import patch +from datetime import datetime +from datetime import timedelta from flask_testing import TestCase +from mock import patch + from grant.app import create_app -from grant.proposal.models import Proposal, ProposalContribution +from grant.extensions import limiter +from grant.milestone.models import Milestone +from grant.proposal.models import Proposal +from grant.settings import PROPOSAL_STAKING_AMOUNT from grant.task.jobs import ProposalReminder from grant.user.models import User, SocialMedia, db, Avatar -from grant.settings import PROPOSAL_STAKING_AMOUNT from grant.utils.enums import ProposalStatus -from grant.extensions import limiter - from .test_data import test_user, test_other_user, test_proposal, mock_blockchain_api_requests @@ -36,7 +39,7 @@ class BaseTestConfig(TestCase): """ message = message or 'HTTP Status %s expected but got %s. Response json: %s' \ - % (status_code, response.status_code, response.json or response.data) + % (status_code, response.status_code, response.json or response.data) self.assertEqual(response.status_code, status_code, message) assert_status = assertStatus @@ -129,6 +132,26 @@ class BaseProposalCreatorConfig(BaseUserConfig): ) self._proposal.team.append(self.user) db.session.add(self._proposal) + db.session.flush() + + milestones = [ + { + "title": "Milestone 1", + "content": "Content 1", + "date_estimated": (datetime.now() + timedelta(days=364)).timestamp(), # random unix time in the future + "payout_percent": 50, + "immediate_payout": True + }, + { + "title": "Milestone 2", + "content": "Content 2", + "date_estimated": (datetime.now() + timedelta(days=365)).timestamp(), # random unix time in the future + "payout_percent": 50, + "immediate_payout": False + } + ] + + Milestone.make(milestones, self._proposal) self._other_proposal = Proposal.create(status=ProposalStatus.DRAFT) self._other_proposal.team.append(self.other_user) diff --git a/backend/tests/proposal/test_api.py b/backend/tests/proposal/test_api.py index d399e369..6f5b05a0 100644 --- a/backend/tests/proposal/test_api.py +++ b/backend/tests/proposal/test_api.py @@ -1,10 +1,10 @@ import json + from mock import patch +from grant.proposal.models import Proposal, db from grant.settings import PROPOSAL_STAKING_AMOUNT -from grant.proposal.models import Proposal from grant.utils.enums import ProposalStatus - from ..config import BaseProposalCreatorConfig from ..test_data import test_proposal, mock_blockchain_api_requests, mock_invalid_address @@ -62,6 +62,51 @@ class TestProposalAPI(BaseProposalCreatorConfig): ) self.assert401(resp) + @patch('requests.get', side_effect=mock_blockchain_api_requests) + def test_update_live_proposal_fails(self, mock_get): + self.login_default_user() + self.proposal.status = ProposalStatus.APPROVED + resp = self.app.put("/api/v1/proposals/{}/publish".format(self.proposal.id)) + self.assert200(resp) + self.assertEqual(resp.json["status"], "LIVE") + + resp = self.app.put( + "/api/v1/proposals/{}".format(self.proposal.id), + data=json.dumps(test_proposal), + content_type='application/json' + ) + self.assert400(resp) + + def test_update_pending_proposal_fails(self): + self.login_default_user() + self.proposal.status = ProposalStatus.PENDING + db.session.add(self.proposal) + db.session.commit() + resp = self.app.get("/api/v1/proposals/{}".format(self.proposal.id)) + self.assert200(resp) + self.assertEqual(resp.json["status"], "PENDING") + resp = self.app.put( + "/api/v1/proposals/{}".format(self.proposal.id), + data=json.dumps(test_proposal), + content_type='application/json' + ) + self.assert400(resp) + + def test_update_rejected_proposal_succeeds(self): + self.login_default_user() + self.proposal.status = ProposalStatus.REJECTED + db.session.add(self.proposal) + db.session.commit() + resp = self.app.get("/api/v1/proposals/{}".format(self.proposal.id)) + self.assert200(resp) + self.assertEqual(resp.json["status"], "REJECTED") + resp = self.app.put( + "/api/v1/proposals/{}".format(self.proposal.id), + data=json.dumps(test_proposal), + content_type='application/json' + ) + self.assert200(resp) + def test_invalid_proposal_update_proposal_draft(self): new_title = "Updated!" new_proposal = test_proposal.copy() diff --git a/backend/tests/test_data.py b/backend/tests/test_data.py index 1ea20484..969db449 100644 --- a/backend/tests/test_data.py +++ b/backend/tests/test_data.py @@ -38,7 +38,6 @@ milestones = [ test_proposal = { "team": test_team, - "crowdFundContractAddress": "0x20000", "content": "## My Proposal", "title": "Give Me Money", "brief": "$$$", From d2e8acd70480b4e5f3cf125cc51c7447ab77928d Mon Sep 17 00:00:00 2001 From: Daniel Ternyak Date: Thu, 14 Mar 2019 20:51:51 -0500 Subject: [PATCH 60/72] Fix Logging typo (#377) --- backend/grant/email/send.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/grant/email/send.py b/backend/grant/email/send.py index 8fdd611f..1c09b359 100644 --- a/backend/grant/email/send.py +++ b/backend/grant/email/send.py @@ -367,7 +367,7 @@ def make_envelope(to, type, email_args): if user and 'subscription' in info: sub = info['subscription'] if user and not is_subscribed(user.settings.email_subscriptions, sub): - app.logger.debug(f'Ignoring send_email to {to} of type {type} because user is unsubscribed.') + current_app.logger.debug(f'Ignoring send_email to {to} of type {type} because user is unsubscribed.') return None email = generate_email(type, email_args, user) From ad844263dd5ca371dec701e0268ee1a49a44ba8d Mon Sep 17 00:00:00 2001 From: AMStrix Date: Thu, 14 Mar 2019 20:55:13 -0500 Subject: [PATCH 61/72] Several bugs (#378) * #375 admin long lines in proposal Detail card * #371 comment_reply & proposal_comment email url typo * #367 fix link to RFP from Proposal create, just use id * #368 profile avatar removal, allow_none on /user PUT --- admin/src/components/ProposalDetail/index.less | 2 +- backend/grant/proposal/views.py | 4 ++-- backend/grant/user/views.py | 2 +- frontend/client/components/CreateFlow/Basics.tsx | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/admin/src/components/ProposalDetail/index.less b/admin/src/components/ProposalDetail/index.less index 211703c2..b36ac488 100644 --- a/admin/src/components/ProposalDetail/index.less +++ b/admin/src/components/ProposalDetail/index.less @@ -18,7 +18,7 @@ font-size: 0.7rem; position: absolute; opacity: 0.8; - top: 1rem; + bottom: -0.7rem; } } diff --git a/backend/grant/proposal/views.py b/backend/grant/proposal/views.py index 576f508f..6d4e5d84 100644 --- a/backend/grant/proposal/views.py +++ b/backend/grant/proposal/views.py @@ -142,7 +142,7 @@ def post_proposal_comments(proposal_id, comment, parent_comment_id): send_email(member.email_address, 'proposal_comment', { 'author': g.current_user, 'proposal': proposal, - 'comment_url': make_url(f'/proposal/{proposal.id}?tab=discussions&comment={comment.id}'), + 'comment_url': make_url(f'/proposals/{proposal.id}?tab=discussions&comment={comment.id}'), 'author_url': make_url(f'/profile/{comment.author.id}'), }) # Email parent comment creator, if it's not themselves @@ -150,7 +150,7 @@ def post_proposal_comments(proposal_id, comment, parent_comment_id): send_email(parent.author.email_address, 'comment_reply', { 'author': g.current_user, 'proposal': proposal, - 'comment_url': make_url(f'/proposal/{proposal.id}?tab=discussions&comment={comment.id}'), + 'comment_url': make_url(f'/proposals/{proposal.id}?tab=discussions&comment={comment.id}'), 'author_url': make_url(f'/profile/{comment.author.id}'), }) diff --git a/backend/grant/user/views.py b/backend/grant/user/views.py index 14b90f3a..654b15c2 100644 --- a/backend/grant/user/views.py +++ b/backend/grant/user/views.py @@ -273,7 +273,7 @@ def delete_avatar(url): "displayName": fields.Str(required=True, validate=lambda d: 2 <= len(d) <= 60), "title": fields.Str(required=True, validate=lambda t: 2 <= len(t) <= 60), "socialMedias": fields.List(fields.Dict(), required=True), - "avatar": fields.Str(required=True, validate=lambda d: validators.url(d)) + "avatar": fields.Str(required=True, allow_none=True, validate=lambda d: validators.url(d)) }) def update_user(user_id, display_name, title, social_medias, avatar): user = g.current_user diff --git a/frontend/client/components/CreateFlow/Basics.tsx b/frontend/client/components/CreateFlow/Basics.tsx index 08381fc5..ab064d83 100644 --- a/frontend/client/components/CreateFlow/Basics.tsx +++ b/frontend/client/components/CreateFlow/Basics.tsx @@ -81,7 +81,7 @@ class CreateFlowBasics extends React.Component { description={ <> This proposal is for the open request{' '} - + {rfp.title} . If you didn’t mean to do this, or want to unlink it,{' '} From 0e979dc77cc2fe9e3e3ded275b0ad0673999eb23 Mon Sep 17 00:00:00 2001 From: William O'Beirne Date: Thu, 14 Mar 2019 23:21:28 -0400 Subject: [PATCH 62/72] Repeat bootstrap attempts (#380) * Repeatedly attempt to bootstrap watcher until it happens. * Get rid of first request bootstrap, watcher polls now. --- backend/app.py | 9 --------- blockchain/src/index.ts | 2 +- blockchain/src/webhooks/index.ts | 8 +++++++- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/backend/app.py b/backend/app.py index 8b3d2b29..ca204ca6 100755 --- a/backend/app.py +++ b/backend/app.py @@ -1,14 +1,5 @@ # -*- coding: utf-8 -*- """Create an application instance.""" from grant.app import create_app -from grant.blockchain.bootstrap import send_bootstrap_data app = create_app() - - -@app.before_first_request -def bootstrap_watcher(): - try: - send_bootstrap_data() - except: - print('Failed to send bootstrap data, watcher must be offline') diff --git a/blockchain/src/index.ts b/blockchain/src/index.ts index 05b5a827..bc4c9506 100755 --- a/blockchain/src/index.ts +++ b/blockchain/src/index.ts @@ -16,7 +16,7 @@ async function start() { log.info("============== Starting services =============="); await initNode(); await RestServer.start(); - await Webhooks.start(); + Webhooks.start(); log.info("==============================================="); } diff --git a/blockchain/src/webhooks/index.ts b/blockchain/src/webhooks/index.ts index d7e0e6e7..a6e4bb43 100755 --- a/blockchain/src/webhooks/index.ts +++ b/blockchain/src/webhooks/index.ts @@ -17,7 +17,13 @@ const MIN_BLOCK_CONF = parseInt(env.MINIMUM_BLOCK_CONFIRMATIONS, 10); export async function start() { initScan(); initNotifiers(); - await requestBootstrap(); + + let { startingBlockHeight } = store.getState(); + while (!startingBlockHeight) { + await requestBootstrap(); + await sleep(10000); + startingBlockHeight = store.getState().startingBlockHeight; + } } export function exit() { From 1d07997a7ae71a88ee157401372c501b2e1acbca Mon Sep 17 00:00:00 2001 From: AMStrix Date: Thu, 14 Mar 2019 22:26:28 -0500 Subject: [PATCH 63/72] Moar bugs (#379) * #376 fix milestone passed to milestone_paid email * #374 fix admin contribution PUT/POST status validation * #373 fix updates & contributors tabs of Proposal --- backend/grant/admin/example_emails.py | 2 + backend/grant/admin/views.py | 5 ++- backend/grant/email/send.py | 2 +- .../templates/emails/milestone_paid.html | 2 +- .../grant/templates/emails/milestone_paid.txt | 2 +- .../client/components/Placeholder/index.tsx | 2 +- .../Proposal/Contributors/index.less | 12 +++-- .../Proposal/Contributors/index.tsx | 45 ++++++++++--------- .../components/Proposal/Updates/index.tsx | 3 +- 9 files changed, 42 insertions(+), 33 deletions(-) diff --git a/backend/grant/admin/example_emails.py b/backend/grant/admin/example_emails.py index 076d034a..67f2c706 100644 --- a/backend/grant/admin/example_emails.py +++ b/backend/grant/admin/example_emails.py @@ -37,6 +37,7 @@ class FakeUpdate(object): user = FakeUser() proposal = FakeProposal() +milestone = FakeMilestone() contribution = FakeContribution() update = FakeUpdate() @@ -160,6 +161,7 @@ example_email_args = { }, 'milestone_paid': { 'proposal': proposal, + 'milestone': milestone, 'amount': '33', 'tx_explorer_url': 'http://someblockexplorer.com/tx/271857129857192579125', 'proposal_milestones_url': 'http://zfnd.org/proposals/999-my-proposal?tab=milestones', diff --git a/backend/grant/admin/views.py b/backend/grant/admin/views.py index a3397c72..7dfd0797 100644 --- a/backend/grant/admin/views.py +++ b/backend/grant/admin/views.py @@ -432,6 +432,7 @@ def paid_milestone_payout_request(id, mid, tx_id): for member in proposal.team: send_email(member.email_address, 'milestone_paid', { 'proposal': proposal, + 'milestone': ms, 'amount': amount, 'tx_explorer_url': f'{EXPLORER_URL}transactions/{tx_id}', 'proposal_milestones_url': make_url(f'/proposals/{proposal.id}?tab=milestones'), @@ -568,7 +569,7 @@ def get_contributions(page, filters, search, sort): @body({ "proposalId": fields.Int(required=True), "userId": fields.Int(required=True), - "status": fields.Str(required=True, validate=validate.OneOf(choices=RFPStatus.list())), + "status": fields.Str(required=True, validate=validate.OneOf(choices=ContributionStatus.list())), "amount": fields.Str(required=True), "txId": fields.Str(required=False, missing=None) }) @@ -607,7 +608,7 @@ def get_contribution(contribution_id): @body({ "proposalId": fields.Int(required=False, missing=None), "userId": fields.Int(required=False, missing=None), - "status": fields.Str(required=True, validate=validate.OneOf(choices=RFPStatus.list())), + "status": fields.Str(required=True, validate=validate.OneOf(choices=ContributionStatus.list())), "amount": fields.Str(required=False, missing=None), "txId": fields.Str(required=False, missing=None), "refundTxId": fields.Str(required=False, allow_none=True, missing=None), diff --git a/backend/grant/email/send.py b/backend/grant/email/send.py index 1c09b359..8121e567 100644 --- a/backend/grant/email/send.py +++ b/backend/grant/email/send.py @@ -269,7 +269,7 @@ def milestone_accept(email_args): def milestone_paid(email_args): p = email_args['proposal'] a = email_args['amount'] - ms = p.current_milestone + ms = email_args['milestone'] return { 'subject': f'{p.title} - {ms.title} has been paid!', 'title': f'Milestone paid', diff --git a/backend/grant/templates/emails/milestone_paid.html b/backend/grant/templates/emails/milestone_paid.html index 94084e60..572aee3c 100644 --- a/backend/grant/templates/emails/milestone_paid.html +++ b/backend/grant/templates/emails/milestone_paid.html @@ -1,7 +1,7 @@

Hooray! {{ args.amount }} ZEC has been paid out for - {{ args.proposal.title }} - {{ args.proposal.current_milestone.title }}! You can view the transaction below:

diff --git a/backend/grant/templates/emails/milestone_paid.txt b/backend/grant/templates/emails/milestone_paid.txt index d2177a85..2fc1044b 100644 --- a/backend/grant/templates/emails/milestone_paid.txt +++ b/backend/grant/templates/emails/milestone_paid.txt @@ -1,4 +1,4 @@ -Hooray! {{args.amount}} ZEC has been paid out for "{{ args.proposal.title }} - {{args.proposal.current_milestone.title }}"! +Hooray! {{args.amount}} ZEC has been paid out for "{{ args.proposal.title }} - {{args.milestone.title }}"! You can view the transaction below: {{ args.tx_explorer_url }} diff --git a/frontend/client/components/Placeholder/index.tsx b/frontend/client/components/Placeholder/index.tsx index 618fb556..e6626cc6 100644 --- a/frontend/client/components/Placeholder/index.tsx +++ b/frontend/client/components/Placeholder/index.tsx @@ -10,7 +10,7 @@ interface Props { } const Placeholder: React.SFC = ({ style = {}, title, subtitle, loading }) => ( -
+
{(loading && ) || ( <> {title &&

{title}

} diff --git a/frontend/client/components/Proposal/Contributors/index.less b/frontend/client/components/Proposal/Contributors/index.less index b0eef11c..a27ce9cf 100644 --- a/frontend/client/components/Proposal/Contributors/index.less +++ b/frontend/client/components/Proposal/Contributors/index.less @@ -2,23 +2,27 @@ .ProposalContributors { display: flex; + max-width: 880px; @media @mobile-query { flex-direction: column; } + .Placeholder { + flex-grow: 1; + } + &-block { flex: 1; padding: 1.5rem; margin: 1rem; - background: #FFF; + background: #fff; border-radius: 4px; border: 1px solid #ddd; - box-shadow: 0 2px 3px rgba(0, 0, 0, 0.1), - 0 1px 1px rgba(0, 0, 0, 0.1); + box-shadow: 0 2px 3px rgba(0, 0, 0, 0.1), 0 1px 1px rgba(0, 0, 0, 0.1); &:first-child { - margin-left: 0; + margin-left: 0; } &:last-child { margin-right: 0; diff --git a/frontend/client/components/Proposal/Contributors/index.tsx b/frontend/client/components/Proposal/Contributors/index.tsx index ea9ae88e..d1d37989 100644 --- a/frontend/client/components/Proposal/Contributors/index.tsx +++ b/frontend/client/components/Proposal/Contributors/index.tsx @@ -3,7 +3,6 @@ import { connect } from 'react-redux'; import UserRow from 'components/UserRow'; import Placeholder from 'components/Placeholder'; import UnitDisplay from 'components/UnitDisplay'; -import Loader from 'components/Loader'; import { toZat } from 'utils/units'; import { fetchProposalContributions } from 'modules/proposals/actions'; import { @@ -54,20 +53,26 @@ class ProposalContributors extends React.Component {
+} + extra={ + <> + + + + } />
- ) - content = <> -
-

Latest contributors

- {contributions.latest.map(makeContributionRow)} -
-
-

Top contributors

- {contributions.top.map(makeContributionRow)} -
- ; + ); + content = ( + <> +
+

Latest contributors

+ {contributions.latest.map(makeContributionRow)} +
+
+

Top contributors

+ {contributions.top.map(makeContributionRow)} +
+ + ); } else { content = ( { ); } } else if (fetchContributionsError) { - content = ; + content = ( + + ); } else { - content = ; + content = ; } - return ( -
- {content} -
- ); + return
{content}
; } -}; +} export default connect( (state: AppState, ownProps: OwnProps) => ({ diff --git a/frontend/client/components/Proposal/Updates/index.tsx b/frontend/client/components/Proposal/Updates/index.tsx index a1ea6411..8f0b2361 100644 --- a/frontend/client/components/Proposal/Updates/index.tsx +++ b/frontend/client/components/Proposal/Updates/index.tsx @@ -1,6 +1,5 @@ import React from 'react'; import { connect } from 'react-redux'; -import Loader from 'components/Loader'; import Markdown from 'components/Markdown'; import moment from 'moment'; import Placeholder from 'components/Placeholder'; @@ -58,7 +57,7 @@ class ProposalUpdates extends React.Component { let content = null; if (isFetchingUpdates) { - content = ; + content = ; } else if (updatesError) { content = ; } else if (updates) { From aff973182d00e00a2f968d497c7598ec2969f93c Mon Sep 17 00:00:00 2001 From: William O'Beirne Date: Thu, 14 Mar 2019 23:37:37 -0400 Subject: [PATCH 64/72] Watcher zero fix (#383) --- blockchain/src/webhooks/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blockchain/src/webhooks/index.ts b/blockchain/src/webhooks/index.ts index a6e4bb43..525e100d 100755 --- a/blockchain/src/webhooks/index.ts +++ b/blockchain/src/webhooks/index.ts @@ -19,7 +19,7 @@ export async function start() { initNotifiers(); let { startingBlockHeight } = store.getState(); - while (!startingBlockHeight) { + while (startingBlockHeight === undefined || startingBlockHeight === null) { await requestBootstrap(); await sleep(10000); startingBlockHeight = store.getState().startingBlockHeight; From 94b8837bdc41d155da7aa2a063b69009bb83eec1 Mon Sep 17 00:00:00 2001 From: William O'Beirne Date: Thu, 14 Mar 2019 23:38:04 -0400 Subject: [PATCH 65/72] Create preview fix + extras (#382) --- .../client/components/CreateFlow/index.tsx | 1 + .../components/Proposal/Milestones/index.tsx | 4 ++- frontend/client/modules/create/utils.ts | 31 +------------------ 3 files changed, 5 insertions(+), 31 deletions(-) diff --git a/frontend/client/components/CreateFlow/index.tsx b/frontend/client/components/CreateFlow/index.tsx index db17670f..8de21124 100644 --- a/frontend/client/components/CreateFlow/index.tsx +++ b/frontend/client/components/CreateFlow/index.tsx @@ -291,6 +291,7 @@ class CreateFlow extends React.Component { private handlePop: History.LocationListener = (location, action) => { if (action === 'POP') { + this.setState({ isPreviewing: false }); const searchValues = qs.parse(location.search); const urlStep = searchValues.step && searchValues.step.toUpperCase(); if (urlStep && CREATE_STEP[urlStep]) { diff --git a/frontend/client/components/Proposal/Milestones/index.tsx b/frontend/client/components/Proposal/Milestones/index.tsx index 5f8aa1e1..eceba29c 100644 --- a/frontend/client/components/Proposal/Milestones/index.tsx +++ b/frontend/client/components/Proposal/Milestones/index.tsx @@ -208,7 +208,9 @@ class ProposalMilestones extends React.Component { const stepSize = milestoneCount > 5 ? 'small' : 'default'; const activeMilestone = proposal.milestones[this.state.step]; - const activeIsCurrent = activeMilestone.id === proposal.currentMilestone!.id; + const activeIsCurrent = proposal.currentMilestone + ? activeMilestone.id === proposal.currentMilestone!.id + : false; return (
): string[] { return warnings; } -function milestoneToMilestoneAmount(milestone: CreateMilestone, raiseGoal: Zat) { - return raiseGoal.divn(100).mul(Zat(milestone.payoutPercent)); -} - -export function proposalToContractData(form: ProposalDraft): any { - const targetInZat = toZat(form.target); - const milestoneAmounts = form.milestones.map(m => - milestoneToMilestoneAmount(m, targetInZat), - ); - const immediateFirstMilestonePayout = form.milestones[0]!.immediatePayout; - - return { - ethAmount: targetInZat, - payoutAddress: form.payoutAddress, - trusteesAddresses: [], - milestoneAmounts, - durationInMinutes: form.deadlineDuration || ONE_DAY * 60, - milestoneVotingPeriodInMinutes: ONE_DAY * 7, - immediateFirstMilestonePayout, - }; -} - // This is kind of a disgusting function, sorry. export function makeProposalPreviewFromDraft(draft: ProposalDraft): ProposalDetail { const { invites, ...rest } = draft; From 2bb67b84d4cb6cc5bcf50862feaa4d6e722da61d Mon Sep 17 00:00:00 2001 From: Daniel Ternyak Date: Thu, 14 Mar 2019 22:41:30 -0500 Subject: [PATCH 66/72] Update README.md --- README.md | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/README.md b/README.md index 09f399f2..da8c6225 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,6 @@ This is a collection of the various services and components that make up the Zca ### Setup ---- - -##### Locally - Instructions for each respective component can be found in: - `/backend/README.md` @@ -15,13 +11,3 @@ Instructions for each respective component can be found in: - `/frontend/README.md` We currently only offer instructions for unix based systems. Windows may or may not be compatible. - -### Testing - -To run tests across all components simultaneously, use the following command - - TBD - -### Deployment - -TBD From 860af087dc24f19ac1534229c57d20b26aa21de7 Mon Sep 17 00:00:00 2001 From: Daniel Ternyak Date: Thu, 14 Mar 2019 22:42:14 -0500 Subject: [PATCH 67/72] Update README.md --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index da8c6225..10185ae5 100644 --- a/README.md +++ b/README.md @@ -10,4 +10,10 @@ Instructions for each respective component can be found in: - `/frontend/README.md` +- `/admin/README.md` + +- `/e2e/README.md` + +- `/blockchain/README.md` + We currently only offer instructions for unix based systems. Windows may or may not be compatible. From 1ad359eddd6e42bd0b7308f419af6b6ef7fe14fc Mon Sep 17 00:00:00 2001 From: Daniel Ternyak Date: Thu, 14 Mar 2019 23:10:43 -0500 Subject: [PATCH 68/72] Delete LICENSE --- backend/LICENSE | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 backend/LICENSE diff --git a/backend/LICENSE b/backend/LICENSE deleted file mode 100644 index 10d1332c..00000000 --- a/backend/LICENSE +++ /dev/null @@ -1,12 +0,0 @@ -Copyright (c) 2018, DevDAO -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - -* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - -* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. - -* Neither the name of DevDAO nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. From 314879e3165ca4b55a65a930797251ff2be598ac Mon Sep 17 00:00:00 2001 From: Daniel Ternyak Date: Thu, 14 Mar 2019 23:11:45 -0500 Subject: [PATCH 69/72] Create LICENSE --- LICENSE | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 LICENSE diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..41185d9b --- /dev/null +++ b/LICENSE @@ -0,0 +1,7 @@ +Copyright 2019 Grant.io Inc + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. From c41d74c0fa23029b1ae4a57b530c2a013b5046f1 Mon Sep 17 00:00:00 2001 From: AMStrix Date: Thu, 14 Mar 2019 23:16:38 -0500 Subject: [PATCH 70/72] String replacement for block explorer URL (#384) use string replace to build block explorer url --- backend/.env.example | 4 ++-- backend/grant/admin/views.py | 5 ++--- backend/grant/proposal/views.py | 8 ++++---- backend/grant/settings.py | 2 +- backend/grant/utils/misc.py | 6 +++++- frontend/.env.example | 4 ++-- frontend/client/utils/formatters.ts | 5 ++++- frontend/config/env.js | 2 +- 8 files changed, 21 insertions(+), 15 deletions(-) diff --git a/backend/.env.example b/backend/.env.example index 2ab25783..736aa204 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -29,8 +29,8 @@ BLOCKCHAIN_REST_API_URL="http://localhost:5051" BLOCKCHAIN_API_SECRET="ef0b48e41f78d3ae85b1379b386f1bca" # Blockchain explorer to link to. Top for mainnet, bottom for testnet. -# EXPLORER_URL="https://explorer.zcha.in/" -EXPLORER_URL="https://testnet.zcha.in/" +# EXPLORER_URL="https://chain.so/tx/ZEC/" +EXPLORER_URL="https://chain.so/tx/ZECTEST/" # Amount for staking a proposal in ZEC PROPOSAL_STAKING_AMOUNT=0.025 diff --git a/backend/grant/admin/views.py b/backend/grant/admin/views.py index 7dfd0797..92ea2f68 100644 --- a/backend/grant/admin/views.py +++ b/backend/grant/admin/views.py @@ -24,7 +24,6 @@ from grant.proposal.models import ( admin_proposal_contributions_schema, ) from grant.rfp.models import RFP, admin_rfp_schema, admin_rfps_schema -from grant.settings import EXPLORER_URL from grant.user.models import User, UserSettings, admin_users_schema, admin_user_schema from grant.utils import pagination from grant.utils.enums import Category @@ -36,7 +35,7 @@ from grant.utils.enums import ( MilestoneStage, RFPStatus, ) -from grant.utils.misc import make_url +from grant.utils.misc import make_url, make_explore_url from .example_emails import example_email_args blueprint = Blueprint('admin', __name__, url_prefix='/api/v1/admin') @@ -434,7 +433,7 @@ def paid_milestone_payout_request(id, mid, tx_id): 'proposal': proposal, 'milestone': ms, 'amount': amount, - 'tx_explorer_url': f'{EXPLORER_URL}transactions/{tx_id}', + 'tx_explorer_url': make_explore_url(tx_id), 'proposal_milestones_url': make_url(f'/proposals/{proposal.id}?tab=milestones'), }) return proposal_schema.dump(proposal), 200 diff --git a/backend/grant/proposal/views.py b/backend/grant/proposal/views.py index 6d4e5d84..3893a98c 100644 --- a/backend/grant/proposal/views.py +++ b/backend/grant/proposal/views.py @@ -11,7 +11,7 @@ from grant.email.send import send_email from grant.milestone.models import Milestone from grant.parser import body, query, paginated_fields from grant.rfp.models import RFP -from grant.settings import EXPLORER_URL, PROPOSAL_STAKING_AMOUNT +from grant.settings import PROPOSAL_STAKING_AMOUNT from grant.task.jobs import ProposalDeadline from grant.user.models import User from grant.utils import pagination @@ -26,7 +26,7 @@ from grant.utils.auth import ( from grant.utils.enums import Category from grant.utils.enums import ProposalStatus, ProposalStage, ContributionStatus from grant.utils.exceptions import ValidationException -from grant.utils.misc import is_email, make_url, from_zat +from grant.utils.misc import is_email, make_url, from_zat, make_explore_url from .models import ( Proposal, proposals_schema, @@ -542,7 +542,7 @@ def post_contribution_confirmation(contribution_id, to, amount, txid): send_email(contribution.user.email_address, 'staking_contribution_confirmed', { 'contribution': contribution, 'proposal': contribution.proposal, - 'tx_explorer_url': f'{EXPLORER_URL}transactions/{txid}', + 'tx_explorer_url': make_explore_url(txid), 'fully_staked': contribution.proposal.is_staked, 'stake_target': str(PROPOSAL_STAKING_AMOUNT.normalize()), }) @@ -553,7 +553,7 @@ def post_contribution_confirmation(contribution_id, to, amount, txid): send_email(contribution.user.email_address, 'contribution_confirmed', { 'contribution': contribution, 'proposal': contribution.proposal, - 'tx_explorer_url': f'{EXPLORER_URL}transactions/{txid}', + 'tx_explorer_url': make_explore_url(txid), }) # Send to the full proposal gang diff --git a/backend/grant/settings.py b/backend/grant/settings.py index 2cdcc2ae..7aa6780a 100644 --- a/backend/grant/settings.py +++ b/backend/grant/settings.py @@ -58,7 +58,7 @@ LINKEDIN_CLIENT_SECRET = env.str("LINKEDIN_CLIENT_SECRET") BLOCKCHAIN_REST_API_URL = env.str("BLOCKCHAIN_REST_API_URL") BLOCKCHAIN_API_SECRET = env.str("BLOCKCHAIN_API_SECRET") -EXPLORER_URL = env.str("EXPLORER_URL", default="https://explorer.zcha.in/") +EXPLORER_URL = env.str("EXPLORER_URL", default="https://chain.so/tx/ZECTEST/") PROPOSAL_STAKING_AMOUNT = Decimal(env.str("PROPOSAL_STAKING_AMOUNT")) diff --git a/backend/grant/utils/misc.py b/backend/grant/utils/misc.py index 1157ac9b..2253eb7e 100644 --- a/backend/grant/utils/misc.py +++ b/backend/grant/utils/misc.py @@ -4,7 +4,7 @@ import re import string import time -from grant.settings import SITE_URL +from grant.settings import SITE_URL, EXPLORER_URL epoch = datetime.datetime.utcfromtimestamp(0) RANDOM_CHARS = string.ascii_letters + string.digits @@ -37,6 +37,10 @@ def make_url(path: str): return f'{SITE_URL}{path}' +def make_explore_url(txid: str): + return EXPLORER_URL.replace('', txid) + + def is_email(email: str): return bool(re.match(r"[^@]+@[^@]+\.[^@]+", email)) diff --git a/frontend/.env.example b/frontend/.env.example index 29dc88b1..186f9d4f 100644 --- a/frontend/.env.example +++ b/frontend/.env.example @@ -13,8 +13,8 @@ BACKEND_URL=http://localhost:5000 # SENTRY_RELEASE="optional, provides sentry logging with release info" # Blockchain explorer to link to. Top for mainnet, bottom for testnet. -# EXPLORER_URL="https://explorer.zcha.in/" -EXPLORER_URL="https://testnet.zcha.in/" +# EXPLORER_URL="https://chain.so/tx/ZEC/" +EXPLORER_URL="https://chain.so/tx/ZECTEST/" # Amount for staking a proposal in ZEC PROPOSAL_STAKING_AMOUNT=0.025 diff --git a/frontend/client/utils/formatters.ts b/frontend/client/utils/formatters.ts index e45ff6d0..87de277c 100644 --- a/frontend/client/utils/formatters.ts +++ b/frontend/client/utils/formatters.ts @@ -87,5 +87,8 @@ export function formatZcashCLI(address: string, amount?: string | number, memo?: } export function formatTxExplorerUrl(txid: string) { - return `${process.env.EXPLORER_URL}transactions/${txid}`; + if (process.env.EXPLORER_URL) { + return process.env.EXPLORER_URL.replace('', txid); + } + throw new Error('EXPLORER_URL env variable needs to be set!'); } diff --git a/frontend/config/env.js b/frontend/config/env.js index 06eaced3..55017af3 100644 --- a/frontend/config/env.js +++ b/frontend/config/env.js @@ -53,7 +53,7 @@ process.env.NODE_PATH = (process.env.NODE_PATH || '') module.exports = () => { const raw = { BACKEND_URL: process.env.BACKEND_URL || 'http://localhost:5000', - EXPLORER_URL: process.env.EXPLORER_URL || 'https://explorer.zcha.in/', + EXPLORER_URL: process.env.EXPLORER_URL || 'https://chain.so/tx/ZECTEST/', NODE_ENV: process.env.NODE_ENV || 'development', PORT: process.env.PORT || 3000, PROPOSAL_STAKING_AMOUNT: process.env.PROPOSAL_STAKING_AMOUNT, From 495b50a9e92b5f5150aef7151158ca015c0ba019 Mon Sep 17 00:00:00 2001 From: Daniel Ternyak Date: Thu, 14 Mar 2019 23:24:10 -0500 Subject: [PATCH 71/72] Misc Fixes (#381) * Fix Logging typo * only allow consecutive milestone date estimates * fix typo * handle empty target * validate max proposal brief size --- backend/grant/proposal/models.py | 3 +- .../components/CreateFlow/Milestones.tsx | 32 ++++++++++++++----- frontend/client/modules/create/utils.ts | 9 ++++-- 3 files changed, 33 insertions(+), 11 deletions(-) diff --git a/backend/grant/proposal/models.py b/backend/grant/proposal/models.py index 4cd9de39..1224ed5a 100644 --- a/backend/grant/proposal/models.py +++ b/backend/grant/proposal/models.py @@ -384,7 +384,7 @@ class Proposal(db.Model): self.brief = brief self.category = category self.content = content - self.target = target + self.target = target if target != '' else None self.payout_address = payout_address self.deadline_duration = deadline_duration Proposal.simple_validate(vars(self)) @@ -590,6 +590,7 @@ class Proposal(db.Model): @hybrid_property def funded(self): + target = Decimal(self.target) # apply matching multiplier funded = Decimal(self.contributed) * Decimal(1 + self.contribution_matching) diff --git a/frontend/client/components/CreateFlow/Milestones.tsx b/frontend/client/components/CreateFlow/Milestones.tsx index 4222589d..15b830eb 100644 --- a/frontend/client/components/CreateFlow/Milestones.tsx +++ b/frontend/client/components/CreateFlow/Milestones.tsx @@ -78,6 +78,11 @@ export default class CreateFlowMilestones extends React.Component milestone={milestone} index={idx} error={errors.milestones && errors.milestones[idx]} + previousMilestoneDateEstimate={ + milestones[idx - 1] && milestones[idx - 1].dateEstimated + ? moment(milestones[idx - 1].dateEstimated * 1000) + : undefined + } onChange={this.handleMilestoneChange} onRemove={this.removeMilestone} /> @@ -96,6 +101,7 @@ export default class CreateFlowMilestones extends React.Component interface MilestoneFieldsProps { index: number; milestone: CreateMilestone; + previousMilestoneDateEstimate: moment.Moment | undefined; error: Falsy | string; onChange(index: number, milestone: CreateMilestone): void; onRemove(index: number): void; @@ -107,6 +113,7 @@ const MilestoneFields = ({ error, onChange, onRemove, + previousMilestoneDateEstimate, }: MilestoneFieldsProps) => (
@@ -154,14 +161,23 @@ const MilestoneFields = ({ allowClear={false} onChange={time => onChange(index, { ...milestone, dateEstimated: time.unix() })} disabled={milestone.immediatePayout} - disabledDate={current => - current - ? current < - moment() - .subtract(1, 'month') - .endOf('month') - : false - } + disabledDate={current => { + if (!previousMilestoneDateEstimate) { + return current + ? current < + moment() + .subtract(1, 'month') + .endOf('month') + : false; + } else { + return current + ? current < + moment() + .subtract(1, 'month') + .endOf('month') || current < previousMilestoneDateEstimate + : false; + } + }} /> 140) { + errors.brief = 'Brief can only be 140 characters maximum'; + } + // Amount to raise const targetFloat = target ? parseFloat(target) : 0; if (target && !Number.isNaN(targetFloat)) { @@ -169,7 +174,7 @@ export function getCreateWarnings(form: Partial): string[] { warnings.push(` You still have pending team invitations. If you publish before they are accepted, your team will be locked in and they won’t be able to - accept join. + join. `); } From ba704f5f5c7c96f8aa9bb915bd829fd02c6dbd94 Mon Sep 17 00:00:00 2001 From: William O'Beirne Date: Fri, 15 Mar 2019 00:45:56 -0400 Subject: [PATCH 72/72] ToS & PP (#385) * Privacy policy * TOS --- frontend/client/static/markdown/PRIVACY.md | 153 +++++++++- frontend/client/static/markdown/TOS.md | 315 ++++++++++++++++++++- 2 files changed, 446 insertions(+), 22 deletions(-) diff --git a/frontend/client/static/markdown/PRIVACY.md b/frontend/client/static/markdown/PRIVACY.md index e0e5ac36..f593e100 100644 --- a/frontend/client/static/markdown/PRIVACY.md +++ b/frontend/client/static/markdown/PRIVACY.md @@ -1,21 +1,152 @@ -# Privacy +# Zcash Privacy Policy -### Lorem Ipsum +This policy describes how **Zcash Foundation** collects, uses, and +shares personal information. You can jump to particular topics by going +to the headings below: -Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. +* [Types Of Information We Collect](#types-of-information-we-collect) +* [Use And Processing Of Information](#use-and-processing-of-information) +* [Sharing Of Information](#sharing-of-information) +* [Your Choices](#your-choices) +* [How We Protect Personal Information](#how-we-protect-personal-information) +* [Miscellaneous](#miscellaneous) +* [Contact Information](#contact-information) -### Duis Aute Irure +## Types Of Information We Collect -Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. +The following provides examples of the type of information that we +collect from you. -Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. +| Context | Types of Data | +|----------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Account Registration | We collect your name and contact information when you create an account in order to provide account related functionalities.,We also collect information relating to the actions that you perform while logged into your account. | +| Cookies and first party tracking | We use cookies and clear GIFs to make our website operate efficiently.,“Cookies” are small pieces of information that a website sends to a device’s hard drive while a website is viewed. | +| Donor Information | We may collect the name and contact information of our donors. | +| Email Interconnectivity | If you receive email from us, we use certain tools to capture data related to when you open our message, click on any links or banners it contains. | +| Employment Information | If you apply for employment with us, we collect information from your application. If you are hired, we may collect additional information needed for employment, such as Social Security Number. | +| Event Registration | We collect your personal information to register you to attend events. | +| Mailing Lists | When you subscribe to an email newsletter or other mailing lists, we collect your email address so we can share announcements and updates with you. | +| Payment Information | Most payments to us are in Zcash. However, in limited scenarios we may collect payment information from you, such as to pay registration fees for an event. | -### Ullamco Laboris Reprehenderit +In addition to the information that we collect from you directly, we may +also receive information about you from other sources, including +publicly available sources. -Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. +## Use and Processing Of Information -### Fugiat +In addition to the purposes and uses described elsewhere in this policy, +we use information in the following ways: -Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. +- To create and administer your account, and to contact you. -**Effective:** Oct 25, 2018 +- To provide you with and improve our services, such as grant collection. + +- To conduct analytics to improve the functionality of our platforms. + This information is never shared with third parties. + +- To respond to inquiries related to support or other requests. + +- To send promotional materials, including announcements and donation + solicitations. + +- For internal administrative purposes, as well as to manage our + relationships. + +## Sharing Of Information. + +In addition to the specific situations discussed elsewhere in this +policy, we disclose information in the following situations: + +1. **Affiliates and Acquisitions**. If another non-profit entity plans + to merge with us, or if another entity acquires our assets, we will + also share information with that entity, including at the + negotiation stage. + +2. **Other Disclosures with Your Consent.** We may ask if you would + like us to share your information with other unaffiliated third + parties who are not described elsewhere in this policy. + +3. **Other Disclosures without Your Consent**. We may disclose + information in response to subpoenas, warrants, or court orders, or + in connection with any legal process, or to comply with relevant + laws. We may also share your information in order to establish or + exercise our rights, to defend against a legal claim, to + investigate, prevent, or take action regarding possible illegal + activities, suspected fraud, safety of person or property, or a + violation of our policies, or to comply with your request for the + shipment of products to or the provision of services by a third + party intermediary. + +4. **Public**. Our website may provide the opportunity to post comments + or discussion, in a public forum. If you decide to submit + information on these pages, that information will be publically + available. + +5. **Service Providers**. We may share your information with service + providers. Among other things service providers may help us to + administer our website, conduct surveys, provide technical support, + process payments, and assist in the fulfillment of orders. Service + providers may only use the information as we direct. + +## Your Choices. + +You can make the following choices regarding your personal information: + +1. **Changes To Your Personal Information**. We rely on you to update + and correct your personal information. Note that we may keep + historical information in our backup files as permitted by law. If + our website does not permit you to update or correct certain + information, contact us at the address described below. + +2. **Online Tracking.** Like most websites, we do not currently + recognize automated browser signals regarding tracking mechanisms, + which may include \"Do Not Track\" instructions. We do try to + minimize tracking. + +3. **Emails**. You may choose to provide us with your email address for + the purpose of allowing us to send newsletters, surveys, and other + promotional materials to you. You can stop receiving promotional + emails by following the unsubscribe instructions in e-mails that you + receive. If you decide not to receive promotional emails, we may + still send you service related communications. + +## How We Protect Personal Information + +No method of transmission over the Internet, or method of electronic +storage, is fully secure. While we use reasonable efforts to protect +your personal information from unauthorized access, use, or disclosure, +we cannot guarantee the security of your personal information. In the +event that we are required by law to inform you of a breach to your +personal information we may notify you electronically, in writing, or by +telephone, if permitted to do so by law. + +Our website allows you to create an account. When you do you will be +prompted to create a password. You are responsible for maintaining the +confidentiality of your password, and you are responsible for any access +to or use of your account by someone else that has obtained your +password, whether or not such access or use has been authorized by you. +You should notify us of any unauthorized use of your password or +account. + +## Miscellaneous + +The following additional information relates to our privacy practices: + +- **Third Party Applications/Websites**. We have no control over the + privacy practices of websites or applications that we do not own. + +- **Changes To This Privacy Policy.** We may change our privacy policy + and practices over time. To the extent that our policy changes in a + material way, the policy that was in place at the time that you + submitted personal information to us will generally govern that + information unless we receive your consent to the new privacy + policy. + +## Contact Information. +If you have any questions concerning our privacy practices, please contact +us at the appropriate address below. We will attempt to respond to your +requests and to provide you with additional privacy-related information. + +**Zcash Foundation** + +**Email**: contact@zfnd.org diff --git a/frontend/client/static/markdown/TOS.md b/frontend/client/static/markdown/TOS.md index e96bfd9f..760c56e0 100644 --- a/frontend/client/static/markdown/TOS.md +++ b/frontend/client/static/markdown/TOS.md @@ -1,21 +1,314 @@ -# Terms of service +# ZF Grants Terms and Conditions -### Lorem Ipsum +Thanks for your interest in ZF Grants. By using this website (the +"Site") and services (together with the Site, the "Services") offered by +the Zcash Foundation through its ZF Grants platform ("ZF Grants," "we," +or "us"), you're agreeing to these legally binding rules (the "Terms"). +One element of the Services are "Grants," which are causes you can +donate to advance distributed ledger and blockchain technology, such as +bounties, research or crowdfunding. Grants may be created by ZF Grants +or other users. ZF Grants may also request proposals for Grants on the +Site. -Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. +Although ZF Grants deals primarily in Zcash, the Electric Coin Company +(previously known as the "Zcash Company") and the underlying Zcash +protocol are not part of ZF Grants and does not participate in the +Services. -### Duis Aute Irure +## 1. Accounts -Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. +You can browse ZF Grants without registering for an account, but to use +some of our Services, you'll need to register and create a password. +When you create and account, the information you give us must be +accurate and complete. You may not impersonate anyone else or choose +names that are offensive or that violate anyone's rights. If you don't +follow these rules, we may cancel your account. -Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. +You're responsible for all the activity on your account, and for keeping +your password confidential. If you find out that someone's used your +account without your permission, you should report it to us using the +contact information on the Site. To sign up for an account, you need to +be at least 18 years old. We may ask you for proof of age. You can +modify the information in your account at any time through your account +settings. We may retain certain information as required by law or as +necessary for our business purposes. All provisions of these Terms +survive termination of an account, including our rights regarding any +Content (as defined below). -### Ullamco Laboris Reprehenderit +## 2. Prohibited Uses -Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. +The following are prohibited: -### Fugiat +- **Illegal Activity.** Don't take any action that infringes or + violates other people's rights, violates the law, or breaches any + contract or legal duty you have toward anyone. -Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. +- **Fraud and Misrepresentation.** Don't post information you know is + false, misleading, or inaccurate. Don't do anything deceptive or + fraudulent. -**Effective:** Oct 25, 2018 +- **Bad Conduct.** Don't do anything threatening, abusive, harassing, + defamatory, libelous, tortious, obscene, profane, or invasive of + another person's privacy. + +- **Spam.** Don't distribute unsolicited or unauthorized advertising + or promotional material, or any junk mail, spam, or chain letters. + Don't run mail lists, listservs, or any kind of auto-responder or + spam on or through the Site. + +- **Malware.** Don't distribute malware, viruses, or anything else + (code, films, programs) designed to interfere with the proper + function of any software, hardware, or equipment on the Site + (whether it belongs to ZF Grants or another party). + +- **Interference.** Don't try to interfere with the proper workings of + the Services. Don't bypass any measures we've put in place to secure + the Services. Don't try to damage or get unauthorized access to any + system, data, password, or other information, whether it belongs to + ZF Grants or another party. Don't take any action that imposes an + unreasonable load on our infrastructure, or on our third-party + providers. (We reserve the right to determine what's reasonable.) + Don't use any kind of software or device (whether it's manual or + automated) to "crawl" or "spider" any part of the Site. + +- **Reverse Engineering.** Don't take apart or reverse engineer any + aspect of the Services in an effort to access things like source + code, underlying ideas, or algorithms. + +## 3. How Grants Work + +This is the process when you're **funding a Grant**: + +The Services act as a funding platform for Grants. Users can donate to +Grants posted on the Services as they choose. The Services only allow +for donations and payouts in Zcash. **When you donate, you send Zcash +immediately** and it is settled on the Zcash blockchain. The exact +amount you pledged is the amount ZF Grants will collect. Once a Grant +reaches its funding goal, ZF Grants funds the Grant. If a Grant does not +reach its funding goal, ZF Grant will refund your donation to your +original payment method if possible. Some Grants may allow you donate +anonymously. If you have anonymously donated and the Grant does not +reach its funding goal, your donation will be converted to a general +donation to the Zcash Foundation. + +These are the terms that apply when you're **creating a Grant:** + +When you create a Grant, ZF Grants may require additional information +about you and may obtain information about you from publicly available +sources. We may also request additional information from you for other +purposes, for example when such information is necessary to comply with +applicable law. All such information is governed by our Privacy Policy. +ZF Grants may require that you agree to additional terms and conditions +specific to your Grant before your Grant is posted on the Site. ZF +Grants has sole discretion to determine when your Grant goal has been +fulfilled or when a certain milestone has been reached. Your Grant +reaching its funding goal does not obligate ZF Grants to fund your +Grant, ZF Grants reserves its right to withhold funding if we feel you +have violated these Terms or that your Grant is detrimental to ZF +Grants, Zcash, or other relevant technologies. You understand that +Grants are funded in Zcash and thus the value of Grant funding when +converted to other currency may fluctuate due to fluctuations in the +value of Zcash. + +## 4. ZF Grant Responsibilities + +ZF Grants isn't liable for any damages or losses related to your use of +the Services. We don't become involved in disputes between users, or +between users and any third party relating to the use of the Services. +We don't endorse any content users submit to the Site. When you use the +Services, you release ZF Grants from claims, damages, and demands of +every kind --- known or unknown, suspected or unsuspected, disclosed or +undisclosed --- arising out of or in any way related to such disputes +and the Services. All content you access through the Services is at your +own risk. You're solely responsible for any resulting damage or loss to +any party. + +## 5. Other Websites + +The Site may contain links to other websites. When you access +third-party websites, you do so at your own risk. We don't control or +endorse those sites. Information you provide to those third parties is +governed under their privacy policy, not ours. When you fund a Grant, +you're also agreeing to the payment processor's terms of service and +privacy policy. + +## 6. Your Intellectual Property + +ZF Grants doesn't own content you submit to us (your "Content"). But we +do need certain licenses from you in order to perform our Services. When +you provide Content, you agree: + +- **We can use your Content.** You grant to us, and others acting on + our behalf, the worldwide, non-exclusive, perpetual, irrevocable, + royalty-free, sublicensable, transferable right to use, exercise, + commercialize, and exploit the copyright, publicity, trademark, and + database rights with respect to your Content. + +- **We can make changes to Content.** You grant us the right to edit, + modify, reformat, excerpt, delete, or translate any of your Content. + +- **You have the right to submit the Content for our use.** Your + Content will not contain third-party copyrighted material, or + material that is subject to other third-party proprietary rights, + unless you have permission from the rightful owner of the material, + or you are otherwise legally entitled to post the material (and to + grant ZF Grants all the license rights outlined here). By submitting + Content, you represent that you are legally allowed to share the + Content with ZF Grants. You will pay all royalties and other amounts + owed to any person or entity based on your Content, or on ZF Grants' + hosting of that Content. + +- **That if we use the Content we're not violating anyone's rights or + copyrights.** If ZF Grants or its users exploit or make use of + Content in the ways contemplated here, you represent that this will + not infringe or violate the rights of any third party, including + (without limitation) any privacy rights, publicity rights, + copyrights, contract rights, or any other intellectual property or + proprietary rights. + +- **You're responsible for the Content.** All information and Content + submitted to the Site, whether publicly posted or privately + transmitted, is the sole responsibility of the person from whom that + content originated. + +## 7. ZF Grants's Intellectual Property + +ZF Grants' Services are legally protected in various ways, including +copyrights, trademarks, service marks, patents, trade secrets, and other +rights and laws. You agree not to infringe on any rights of ZF Grants +and to respect all copyright and other legal notices, information, and +restrictions contained in any content accessed through the Site. You +also agree not to change, translate, or otherwise create derivative +works of the Service. + +ZF Grants grants you revocable, worldwide, non-exclusive, +non-sublicensable, and non-transferable license to reproduce content +from the Services for personal use only. If you want to use, reproduce, +modify, distribute, or store any content for a commercial purpose, you +need prior written permission from ZF Grants. A "commercial purpose" +means you intend to use, sell, license, rent, or otherwise exploit +content for commercial use, in any way. + +## 8. Copyright Notices + +The Digital Millennium Copyright Act lays out a system of legal +requirements for dealing with allegations of copyright infringement. ZF +Grants complies with the DMCA, and we respond to notices of alleged +infringement if they comply with the law and our reasonable requirements +necessary to investigate the compliant. We reserve the right to delete +or disable content alleged to be infringing, and to terminate accounts +for repeat infringers. We do this when appropriate and at our sole +discretion. + +If you'd like to submit a claim of copyright infringement, our +designated agent for notice of alleged copyright infringement is: + +Zcash Foundation + +1390 Chain Bridge Road, \#A132 + +McLean, VA 22101 + +contact@zfnd.org + +## 9. Our Rights + +ZF Grants reserves these rights: + +- We can make changes to the ZF Grants Site and Services without + notice or liability. + +- We have the right to decide who's eligible to use ZF Grants. We can + cancel accounts or decline to offer our Services. If these things + are prohibited by law where you live, then we revoke your right to + use ZF Grants in that jurisdiction. + +- We have the right to cancel any donation to any Grant, at any time + and for any reason. + +- We have the right to reject, cancel, interrupt, remove, or suspend + any Grant at any time and for any reason. + +ZF Grants is not liable for any damages as a result of any of these +actions. + +## 10. Warranty Disclaimer + +You use our Services solely at your own risk. They are provided to you +"as is" and "as available" *and without warranty of any kind*, express +or implied. ZF GRANTS SPECIFICALLY DISCLAIMS ANY AND ALL WARRANTIES AND +CONDITIONS OF MERCHANTABILITY, NON-INFRINGEMENT, AND FITNESS FOR A +PARTICULAR PURPOSE, AND ANY WARRANTIES IMPLIED BY ANY COURSE OF DEALING, +COURSE OF PERFORMANCE, OR USAGE OF TRADE. NO ADVICE OR INFORMATION (ORAL +OR WRITTEN) OBTAINED BY YOU FROM ZF GRANTS SHALL CREATE ANY WARRANTY. ZF +GRANTS DISCLAIMS ANY WARRANTY OF ACCURACY RELATING TO ALL CONTENT AND +INFORMATION PROVIDED ON THE SITE OF FROM ZF GRANTS. + +## 11. Indemnification + +You agree to defend, indemnify, and hold us harmless from all +liabilities, claims, and expenses (including reasonable attorneys' fees +and other legal costs) that arise from or relate to your use or misuse +of ZF Grants or breach of these Terms. We reserve the right to assume +the exclusive defense and control of any matter otherwise subject to +this indemnification clause, in which case you agree that you'll +cooperate and help us in asserting any defenses. + +## 12. Limitation of Liability + +**TO THE FULLEST EXTENT PERMITTED BY LAW, IN NO EVENT WILL ZF GRANTS, +ITS DIRECTORS, EMPLOYEES, PARTNERS, SUPPLIERS, OR CONTENT PROVIDERS BE +LIABLE FOR ANY INDIRECT, INCIDENTAL, PUNITIVE, CONSEQUENTIAL, SPECIAL, +OR EXEMPLARY DAMAGES OF ANY KIND, INCLUDING BUT NOT LIMITED TO DAMAGES +(I) RESULTING FROM YOUR ACCESS TO, USE OF, OR INABILITY TO ACCESS OR USE +THE SERVICES; (II) FOR ANY LOST PROFITS, DATA LOSS, OR COST OF +PROCUREMENT OR SUBSTITUTE GOODS OR SERVICES; OR (III) FOR ANY CONDUCT OF +CONTENT OF ANY THIRD PARTY ON THE SITE. IN NO EVENT SHALL ZF GRANTS' +LIABILITY FOR DIRECT DAMAGES BE IN EXCESS OF (IN THE AGGREGATE) ONE +HUNDRED U.S. DOLLARS (\$100.00). YOU AGREE THAT THIS LIMITATION OF +LIABILITY IS A MATERIAL PART OF THESE TERMS AND THAT ZF GRANTS WOULD NOT +PROVIDE THE SERVICES WITHOUT IT.** + +## 13. Dispute Resolution and Governing Law + +ZF Grants encourages you to contact us if you're having an issue before +resorting to the courts. In the unfortunate situation where legal action +does arise, these Terms (and all other rules, policies, or guidelines +incorporated by reference or posted by ZF Grants) will be governed by +and construed in accordance with the laws of the State of Colorado and +the United States, without giving effect to any principles of conflicts +of law, and without application of the Uniform Computer Information +Transaction Act or the United Nations Convention of Controls for +International Sale of Goods. You agree that ZF Grants and its Services +are deemed a passive website that does not give rise to jurisdiction +over ZF Grants or its parents, subsidiaries, affiliates, assigns, +employees, agents, directors, officers, or shareholders, either specific +or general, in any jurisdiction other than the State of Colorado. You +agree that any action at law or in equity arising out of or relating to +these Terms, or your use or non-use of ZF Grants, shall be filed only in +the state or federal courts located in Denver, Colorado, and you hereby +consent and submit to the personal jurisdiction of these courts for the +purposes of litigating any such action. You hereby irrevocably waive any +right you may have to trial by jury in any dispute, action, or +proceeding. + +## 14. Other Terms + +These Terms and the other material referenced in them are the entire +agreement between you and ZF Grants with respect to the Services. They +supersede all other communications and proposals (whether oral, written, +or electronic) between you and ZF Grants with respect to the Services +and govern our future relationship. If any provision of these Terms is +found to be invalid under the law, that provision will be limited or +eliminated to the minimum extent necessary so that the Terms otherwise +will remain in full force and effect and enforceable. The failure of +either you or ZF Grants to exercise any right provided for in these +Terms in any way won't be deemed a waiver of any other rights. These +Terms are personal to you. You can't assign them, transfer them, or +sublicense them unless you get ZF Grants' prior written consent. ZF +Grants has the right to assign, transfer, or delegate any of its rights +and obligations under these Terms without your consent. ZF Grants will +provide you notice via email, written notice, or by conspicuously +posting the notice on our Site. We may change these Terms from time to +time, and may choose to notify you of material changes to them via email +or by posting notice on the Site.