parent
22487b331b
commit
a418f3d5b6
|
@ -68,3 +68,5 @@ dump.rdb
|
||||||
# jetbrains
|
# jetbrains
|
||||||
.idea/
|
.idea/
|
||||||
|
|
||||||
|
# vscode
|
||||||
|
.vscode/
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from flask import Blueprint
|
from flask import Blueprint, g
|
||||||
from flask_yoloapi import endpoint, parameter
|
from flask_yoloapi import endpoint, parameter
|
||||||
from sqlalchemy.exc import IntegrityError
|
from sqlalchemy.exc import IntegrityError
|
||||||
|
|
||||||
from grant.comment.models import Comment, comment_schema
|
from grant.comment.models import Comment, comment_schema
|
||||||
from grant.milestone.models import Milestone
|
from grant.milestone.models import Milestone
|
||||||
from grant.user.models import User, SocialMedia, Avatar
|
from grant.user.models import User, SocialMedia, Avatar
|
||||||
|
from grant.utils.auth import requires_sm, requires_team_member_auth
|
||||||
from .models import Proposal, proposals_schema, proposal_schema, ProposalUpdate, proposal_update_schema, db
|
from .models import Proposal, proposals_schema, proposal_schema, ProposalUpdate, proposal_update_schema, db
|
||||||
|
|
||||||
blueprint = Blueprint("proposal", __name__, url_prefix="/api/v1/proposals")
|
blueprint = Blueprint("proposal", __name__, url_prefix="/api/v1/proposals")
|
||||||
|
@ -39,28 +40,22 @@ def get_proposal_comments(proposal_id):
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route("/<proposal_id>/comments", methods=["POST"])
|
@blueprint.route("/<proposal_id>/comments", methods=["POST"])
|
||||||
|
@requires_sm
|
||||||
@endpoint.api(
|
@endpoint.api(
|
||||||
parameter('userId', type=int, required=True),
|
|
||||||
parameter('content', type=str, required=True)
|
parameter('content', type=str, required=True)
|
||||||
)
|
)
|
||||||
def post_proposal_comments(proposal_id, user_id, content):
|
def post_proposal_comments(proposal_id, user_id, content):
|
||||||
proposal = Proposal.query.filter_by(id=proposal_id).first()
|
proposal = Proposal.query.filter_by(id=proposal_id).first()
|
||||||
if proposal:
|
if proposal:
|
||||||
user = User.query.filter_by(id=user_id).first()
|
comment = Comment(
|
||||||
|
proposal_id=proposal_id,
|
||||||
if user:
|
user_id=g.current_user.id,
|
||||||
comment = Comment(
|
content=content
|
||||||
proposal_id=proposal_id,
|
)
|
||||||
user_id=user_id,
|
db.session.add(comment)
|
||||||
content=content
|
db.session.commit()
|
||||||
)
|
dumped_comment = comment_schema.dump(comment)
|
||||||
db.session.add(comment)
|
return dumped_comment, 201
|
||||||
db.session.commit()
|
|
||||||
dumped_comment = comment_schema.dump(comment)
|
|
||||||
return dumped_comment, 201
|
|
||||||
|
|
||||||
else:
|
|
||||||
return {"message": "No user matching id"}, 404
|
|
||||||
else:
|
else:
|
||||||
return {"message": "No proposal matching id"}, 404
|
return {"message": "No proposal matching id"}, 404
|
||||||
|
|
||||||
|
@ -73,8 +68,8 @@ def get_proposals(stage):
|
||||||
if stage:
|
if stage:
|
||||||
proposals = (
|
proposals = (
|
||||||
Proposal.query.filter_by(stage=stage)
|
Proposal.query.filter_by(stage=stage)
|
||||||
.order_by(Proposal.date_created.desc())
|
.order_by(Proposal.date_created.desc())
|
||||||
.all()
|
.all()
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
proposals = Proposal.query.order_by(Proposal.date_created.desc()).all()
|
proposals = Proposal.query.order_by(Proposal.date_created.desc()).all()
|
||||||
|
@ -83,6 +78,7 @@ def get_proposals(stage):
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route("/", methods=["POST"])
|
@blueprint.route("/", methods=["POST"])
|
||||||
|
@requires_sm
|
||||||
@endpoint.api(
|
@endpoint.api(
|
||||||
parameter('crowdFundContractAddress', type=str, required=True),
|
parameter('crowdFundContractAddress', type=str, required=True),
|
||||||
parameter('content', type=str, required=True),
|
parameter('content', type=str, required=True),
|
||||||
|
@ -92,7 +88,6 @@ def get_proposals(stage):
|
||||||
parameter('team', type=list, required=True)
|
parameter('team', type=list, required=True)
|
||||||
)
|
)
|
||||||
def make_proposal(crowd_fund_contract_address, content, title, milestones, category, team):
|
def make_proposal(crowd_fund_contract_address, content, title, milestones, category, team):
|
||||||
from grant.user.models import User
|
|
||||||
existing_proposal = Proposal.query.filter_by(proposal_address=crowd_fund_contract_address).first()
|
existing_proposal = Proposal.query.filter_by(proposal_address=crowd_fund_contract_address).first()
|
||||||
if existing_proposal:
|
if existing_proposal:
|
||||||
return {"message": "Oops! Something went wrong."}, 409
|
return {"message": "Oops! Something went wrong."}, 409
|
||||||
|
@ -187,24 +182,21 @@ def get_proposal_update(proposal_id, update_id):
|
||||||
return {"message": "No proposal matching id"}, 404
|
return {"message": "No proposal matching id"}, 404
|
||||||
|
|
||||||
|
|
||||||
# TODO: Add authentication to endpoint
|
|
||||||
@blueprint.route("/<proposal_id>/updates", methods=["POST"])
|
@blueprint.route("/<proposal_id>/updates", methods=["POST"])
|
||||||
|
@requires_team_member_auth
|
||||||
|
@requires_sm
|
||||||
@endpoint.api(
|
@endpoint.api(
|
||||||
parameter('title', type=str, required=True),
|
parameter('title', type=str, required=True),
|
||||||
parameter('content', type=str, required=True)
|
parameter('content', type=str, required=True)
|
||||||
)
|
)
|
||||||
def post_proposal_update(proposal_id, title, content):
|
def post_proposal_update(proposal_id, title, content):
|
||||||
proposal = Proposal.query.filter_by(id=proposal_id).first()
|
update = ProposalUpdate(
|
||||||
if proposal:
|
proposal_id=g.current_proposal.id,
|
||||||
update = ProposalUpdate(
|
title=title,
|
||||||
proposal_id=proposal.id,
|
content=content
|
||||||
title=title,
|
)
|
||||||
content=content
|
db.session.add(update)
|
||||||
)
|
db.session.commit()
|
||||||
db.session.add(update)
|
|
||||||
db.session.commit()
|
|
||||||
|
|
||||||
dumped_update = proposal_update_schema.dump(update)
|
dumped_update = proposal_update_schema.dump(update)
|
||||||
return dumped_update, 201
|
return dumped_update, 201
|
||||||
else:
|
|
||||||
return {"message": "No proposal matching id"}, 404
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
from sqlalchemy import func
|
||||||
from grant.comment.models import Comment
|
from grant.comment.models import Comment
|
||||||
from grant.email.models import EmailVerification
|
from grant.email.models import EmailVerification
|
||||||
from grant.extensions import ma, db
|
from grant.extensions import ma, db
|
||||||
|
@ -57,7 +58,7 @@ class User(db.Model):
|
||||||
self.title = title
|
self.title = title
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def create(email_address=None, account_address=None, display_name=None, title=None):
|
def create(email_address=None, account_address=None, display_name=None, title=None, _send_email=True):
|
||||||
user = User(
|
user = User(
|
||||||
account_address=account_address,
|
account_address=account_address,
|
||||||
email_address=email_address,
|
email_address=email_address,
|
||||||
|
@ -72,21 +73,22 @@ class User(db.Model):
|
||||||
db.session.add(ev)
|
db.session.add(ev)
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
send_email(user.email_address, 'signup', {
|
if send_email:
|
||||||
'display_name': user.display_name,
|
send_email(user.email_address, 'signup', {
|
||||||
'confirm_url': make_url(f'/email/verify?code={ev.code}')
|
'display_name': user.display_name,
|
||||||
})
|
'confirm_url': make_url(f'/email/verify?code={ev.code}')
|
||||||
|
})
|
||||||
|
|
||||||
return user
|
return user
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_by_email_or_account_address(email_address: str = None, account_address: str = None):
|
def get_by_identifier(email_address: str = None, account_address: str = None):
|
||||||
if not email_address and not account_address:
|
if not email_address and not account_address:
|
||||||
raise ValueError("Either email_address or account_address is required to get a user")
|
raise ValueError("Either email_address or account_address is required to get a user")
|
||||||
|
|
||||||
return User.query.filter(
|
return User.query.filter(
|
||||||
(User.account_address == account_address) |
|
(func.lower(User.account_address) == func.lower(account_address)) |
|
||||||
(User.email_address == email_address)
|
(func.lower(User.email_address) == func.lower(email_address))
|
||||||
).first()
|
).first()
|
||||||
|
|
||||||
class UserSchema(ma.Schema):
|
class UserSchema(ma.Schema):
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
from flask import Blueprint, g
|
from flask import Blueprint, g
|
||||||
from flask_yoloapi import endpoint, parameter
|
from flask_yoloapi import endpoint, parameter
|
||||||
|
|
||||||
|
|
||||||
from .models import User, SocialMedia, Avatar, users_schema, user_schema, db
|
|
||||||
from grant.proposal.models import Proposal, proposal_team
|
from grant.proposal.models import Proposal, proposal_team
|
||||||
from grant.utils.auth import requires_sm, verify_signed_auth, BadSignatureException
|
from grant.utils.auth import requires_sm, requires_same_user_auth, verify_signed_auth, BadSignatureException
|
||||||
|
from .models import User, SocialMedia, Avatar, users_schema, user_schema, db
|
||||||
|
|
||||||
blueprint = Blueprint('user', __name__, url_prefix='/api/v1/users')
|
blueprint = Blueprint('user', __name__, url_prefix='/api/v1/users')
|
||||||
|
|
||||||
|
@ -35,7 +34,7 @@ def get_me():
|
||||||
@blueprint.route("/<user_identity>", methods=["GET"])
|
@blueprint.route("/<user_identity>", methods=["GET"])
|
||||||
@endpoint.api()
|
@endpoint.api()
|
||||||
def get_user(user_identity):
|
def get_user(user_identity):
|
||||||
user = User.get_by_email_or_account_address(email_address=user_identity, account_address=user_identity)
|
user = User.get_by_identifier(email_address=user_identity, account_address=user_identity)
|
||||||
if user:
|
if user:
|
||||||
result = user_schema.dump(user)
|
result = user_schema.dump(user)
|
||||||
return result
|
return result
|
||||||
|
@ -54,14 +53,14 @@ def get_user(user_identity):
|
||||||
parameter('rawTypedData', type=str, required=True)
|
parameter('rawTypedData', type=str, required=True)
|
||||||
)
|
)
|
||||||
def create_user(
|
def create_user(
|
||||||
account_address,
|
account_address,
|
||||||
email_address,
|
email_address,
|
||||||
display_name,
|
display_name,
|
||||||
title,
|
title,
|
||||||
signed_message,
|
signed_message,
|
||||||
raw_typed_data
|
raw_typed_data
|
||||||
):
|
):
|
||||||
existing_user = User.get_by_email_or_account_address(email_address=email_address, account_address=account_address)
|
existing_user = User.get_by_identifier(email_address=email_address, account_address=account_address)
|
||||||
if existing_user:
|
if existing_user:
|
||||||
return {"message": "User with that address or email already exists"}, 409
|
return {"message": "User with that address or email already exists"}, 409
|
||||||
|
|
||||||
|
@ -70,11 +69,11 @@ def create_user(
|
||||||
sig_address = verify_signed_auth(signed_message, raw_typed_data)
|
sig_address = verify_signed_auth(signed_message, raw_typed_data)
|
||||||
if sig_address.lower() != account_address.lower():
|
if sig_address.lower() != account_address.lower():
|
||||||
return {
|
return {
|
||||||
"message": "Message signature address ({sig_address}) doesn't match account_address ({account_address})".format(
|
"message": "Message signature address ({sig_address}) doesn't match account_address ({account_address})".format(
|
||||||
sig_address=sig_address,
|
sig_address=sig_address,
|
||||||
account_address=account_address
|
account_address=account_address
|
||||||
)
|
)
|
||||||
}, 400
|
}, 400
|
||||||
except BadSignatureException:
|
except BadSignatureException:
|
||||||
return {"message": "Invalid message signature"}, 400
|
return {"message": "Invalid message signature"}, 400
|
||||||
|
|
||||||
|
@ -88,6 +87,7 @@ def create_user(
|
||||||
result = user_schema.dump(user)
|
result = user_schema.dump(user)
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route("/auth", methods=["POST"])
|
@blueprint.route("/auth", methods=["POST"])
|
||||||
@endpoint.api(
|
@endpoint.api(
|
||||||
parameter('accountAddress', type=str, required=True),
|
parameter('accountAddress', type=str, required=True),
|
||||||
|
@ -95,7 +95,7 @@ def create_user(
|
||||||
parameter('rawTypedData', type=str, required=True)
|
parameter('rawTypedData', type=str, required=True)
|
||||||
)
|
)
|
||||||
def auth_user(account_address, signed_message, raw_typed_data):
|
def auth_user(account_address, signed_message, raw_typed_data):
|
||||||
existing_user = User.get_by_email_or_account_address(account_address=account_address)
|
existing_user = User.get_by_identifier(account_address=account_address)
|
||||||
if not existing_user:
|
if not existing_user:
|
||||||
return {"message": "No user exists with that address"}, 400
|
return {"message": "No user exists with that address"}, 400
|
||||||
|
|
||||||
|
@ -103,27 +103,28 @@ def auth_user(account_address, signed_message, raw_typed_data):
|
||||||
sig_address = verify_signed_auth(signed_message, raw_typed_data)
|
sig_address = verify_signed_auth(signed_message, raw_typed_data)
|
||||||
if sig_address.lower() != account_address.lower():
|
if sig_address.lower() != account_address.lower():
|
||||||
return {
|
return {
|
||||||
"message": "Message signature address ({sig_address}) doesn't match account_address ({account_address})".format(
|
"message": "Message signature address ({sig_address}) doesn't match account_address ({account_address})".format(
|
||||||
sig_address=sig_address,
|
sig_address=sig_address,
|
||||||
account_address=account_address
|
account_address=account_address
|
||||||
)
|
)
|
||||||
}, 400
|
}, 400
|
||||||
except BadSignatureException:
|
except BadSignatureException:
|
||||||
return {"message": "Invalid message signature"}, 400
|
return {"message": "Invalid message signature"}, 400
|
||||||
|
|
||||||
return user_schema.dump(existing_user)
|
return user_schema.dump(existing_user)
|
||||||
|
|
||||||
|
|
||||||
@blueprint.route("/<user_identity>", methods=["PUT"])
|
@blueprint.route("/<user_identity>", methods=["PUT"])
|
||||||
|
@requires_sm
|
||||||
|
@requires_same_user_auth
|
||||||
@endpoint.api(
|
@endpoint.api(
|
||||||
parameter('displayName', type=str, required=False),
|
parameter('displayName', type=str, required=True),
|
||||||
parameter('title', type=str, required=False),
|
parameter('title', type=str, required=True),
|
||||||
parameter('socialMedias', type=list, required=False),
|
parameter('socialMedias', type=list, required=True),
|
||||||
parameter('avatar', type=dict, required=False),
|
parameter('avatar', type=dict, required=True)
|
||||||
)
|
)
|
||||||
def update_user(user_identity, display_name, title, social_medias, avatar):
|
def update_user(user_identity, display_name, title, social_medias, avatar):
|
||||||
user = User.get_by_email_or_account_address(email_address=user_identity, account_address=user_identity)
|
user = g.current_user
|
||||||
if not user:
|
|
||||||
return {"message": "User with that address or email not found"}, 404
|
|
||||||
|
|
||||||
if display_name is not None:
|
if display_name is not None:
|
||||||
user.display_name = display_name
|
user.display_name = display_name
|
||||||
|
@ -132,11 +133,12 @@ def update_user(user_identity, display_name, title, social_medias, avatar):
|
||||||
user.title = title
|
user.title = title
|
||||||
|
|
||||||
if social_medias is not None:
|
if social_medias is not None:
|
||||||
sm_query = SocialMedia.query.filter_by(user_id=user.id)
|
SocialMedia.query.filter_by(user_id=user.id).delete()
|
||||||
sm_query.delete()
|
|
||||||
for social_media in social_medias:
|
for social_media in social_medias:
|
||||||
sm = SocialMedia(social_media_link=social_media.get("link"), user_id=user.id)
|
sm = SocialMedia(social_media_link=social_media.get("link"), user_id=user.id)
|
||||||
db.session.add(sm)
|
db.session.add(sm)
|
||||||
|
else:
|
||||||
|
SocialMedia.query.filter_by(user_id=user.id).delete()
|
||||||
|
|
||||||
if avatar is not None:
|
if avatar is not None:
|
||||||
Avatar.query.filter_by(user_id=user.id).delete()
|
Avatar.query.filter_by(user_id=user.id).delete()
|
||||||
|
@ -144,8 +146,9 @@ def update_user(user_identity, display_name, title, social_medias, avatar):
|
||||||
if avatar_link:
|
if avatar_link:
|
||||||
avatar_obj = Avatar(image_url=avatar_link, user_id=user.id)
|
avatar_obj = Avatar(image_url=avatar_link, user_id=user.id)
|
||||||
db.session.add(avatar_obj)
|
db.session.add(avatar_obj)
|
||||||
|
else:
|
||||||
|
Avatar.query.filter_by(user_id=user.id).delete()
|
||||||
|
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
|
|
||||||
result = user_schema.dump(user)
|
result = user_schema.dump(user)
|
||||||
return result
|
return result
|
||||||
|
|
|
@ -8,6 +8,7 @@ from itsdangerous import SignatureExpired, BadSignature
|
||||||
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
|
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
|
||||||
|
|
||||||
from grant.settings import SECRET_KEY, AUTH_URL
|
from grant.settings import SECRET_KEY, AUTH_URL
|
||||||
|
from ..proposal.models import Proposal
|
||||||
from ..user.models import User
|
from ..user.models import User
|
||||||
|
|
||||||
TWO_WEEKS = 1209600
|
TWO_WEEKS = 1209600
|
||||||
|
@ -35,6 +36,7 @@ def verify_token(token):
|
||||||
class BadSignatureException(Exception):
|
class BadSignatureException(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def verify_signed_auth(signature, typed_data):
|
def verify_signed_auth(signature, typed_data):
|
||||||
loaded_typed_data = ast.literal_eval(typed_data)
|
loaded_typed_data = ast.literal_eval(typed_data)
|
||||||
url = AUTH_URL + "/message/recover"
|
url = AUTH_URL + "/message/recover"
|
||||||
|
@ -43,29 +45,13 @@ def verify_signed_auth(signature, typed_data):
|
||||||
response = requests.request("POST", url, data=payload, headers=headers)
|
response = requests.request("POST", url, data=payload, headers=headers)
|
||||||
json_response = response.json()
|
json_response = response.json()
|
||||||
recovered_address = json_response.get('recoveredAddress')
|
recovered_address = json_response.get('recoveredAddress')
|
||||||
|
|
||||||
if not recovered_address:
|
if not recovered_address:
|
||||||
raise BadSignatureException("Authorization signature is invalid")
|
raise BadSignatureException("Authorization signature is invalid")
|
||||||
|
|
||||||
return recovered_address
|
return recovered_address
|
||||||
|
|
||||||
|
|
||||||
def requires_auth(f):
|
# Decorator that requires you to have EIP-712 message signature headers for auth
|
||||||
@wraps(f)
|
|
||||||
def decorated(*args, **kwargs):
|
|
||||||
token = request.headers.get('Authorization', None)
|
|
||||||
if token:
|
|
||||||
string_token = token.encode('ascii', 'ignore')
|
|
||||||
user = verify_token(string_token)
|
|
||||||
if user:
|
|
||||||
g.current_user = user
|
|
||||||
return f(*args, **kwargs)
|
|
||||||
|
|
||||||
return jsonify(message="Authentication is required to access this resource"), 401
|
|
||||||
|
|
||||||
return decorated
|
|
||||||
|
|
||||||
|
|
||||||
def requires_sm(f):
|
def requires_sm(f):
|
||||||
@wraps(f)
|
@wraps(f)
|
||||||
def decorated(*args, **kwargs):
|
def decorated(*args, **kwargs):
|
||||||
|
@ -73,13 +59,12 @@ def requires_sm(f):
|
||||||
typed_data = request.headers.get('RawTypedData', None)
|
typed_data = request.headers.get('RawTypedData', None)
|
||||||
|
|
||||||
if typed_data and signature:
|
if typed_data and signature:
|
||||||
auth_address = None
|
|
||||||
try:
|
try:
|
||||||
auth_address = verify_signed_auth(signature, typed_data)
|
auth_address = verify_signed_auth(signature, typed_data)
|
||||||
except BadSignatureException:
|
except BadSignatureException:
|
||||||
return jsonify(message="Invalid auth message signature"), 401
|
return jsonify(message="Invalid auth message signature"), 401
|
||||||
|
|
||||||
user = User.get_by_email_or_account_address(account_address=auth_address)
|
user = User.get_by_identifier(account_address=auth_address)
|
||||||
if not user:
|
if not user:
|
||||||
return jsonify(message="No user exists with address: {}".format(auth_address)), 401
|
return jsonify(message="No user exists with address: {}".format(auth_address)), 401
|
||||||
|
|
||||||
|
@ -89,3 +74,41 @@ def requires_sm(f):
|
||||||
return jsonify(message="Authentication is required to access this resource"), 401
|
return jsonify(message="Authentication is required to access this resource"), 401
|
||||||
|
|
||||||
return decorated
|
return decorated
|
||||||
|
|
||||||
|
|
||||||
|
# Decorator that requires you to be the user you're interacting with
|
||||||
|
def requires_same_user_auth(f):
|
||||||
|
@wraps(f)
|
||||||
|
def decorated(*args, **kwargs):
|
||||||
|
user_identity = kwargs["user_identity"]
|
||||||
|
if not user_identity:
|
||||||
|
return jsonify(message="Decorator requires_same_user_auth requires path variable <user_identity>"), 500
|
||||||
|
|
||||||
|
user = User.get_by_identifier(account_address=user_identity, email_address=user_identity)
|
||||||
|
if user.id != g.current_user.id:
|
||||||
|
return jsonify(message="You are not authorized to modify this user"), 403
|
||||||
|
|
||||||
|
return f(*args, **kwargs)
|
||||||
|
|
||||||
|
return requires_sm(decorated)
|
||||||
|
|
||||||
|
|
||||||
|
# Decorator that requires you to be a team member of a proposal to access
|
||||||
|
def requires_team_member_auth(f):
|
||||||
|
@wraps(f)
|
||||||
|
def decorated(*args, **kwargs):
|
||||||
|
proposal_id = kwargs["proposal_id"]
|
||||||
|
if not proposal_id:
|
||||||
|
return jsonify(message="Decorator requires_team_member_auth requires path variable <proposal_id>"), 500
|
||||||
|
|
||||||
|
proposal = Proposal.query.filter_by(id=proposal_id).first()
|
||||||
|
if not proposal:
|
||||||
|
return jsonify(message="No proposal exists with id: {}".format(proposal_id)), 404
|
||||||
|
|
||||||
|
if not g.current_user in proposal.team:
|
||||||
|
return jsonify(message="You are not authorized to modify this proposal"), 403
|
||||||
|
|
||||||
|
g.current_proposal = proposal
|
||||||
|
return f(*args, **kwargs)
|
||||||
|
|
||||||
|
return requires_sm(decorated)
|
||||||
|
|
|
@ -55,4 +55,4 @@ flask-sendgrid==0.6
|
||||||
sendgrid==5.3.0
|
sendgrid==5.3.0
|
||||||
|
|
||||||
# input validation
|
# input validation
|
||||||
flask-yolo2API
|
flask-yolo2API==0.2.4
|
|
@ -1,6 +1,8 @@
|
||||||
from flask_testing import TestCase
|
from flask_testing import TestCase
|
||||||
|
|
||||||
from grant.app import create_app, db
|
from grant.app import create_app
|
||||||
|
from grant.user.models import User, SocialMedia, db, Avatar
|
||||||
|
from .test_data import test_user, message
|
||||||
|
|
||||||
|
|
||||||
class BaseTestConfig(TestCase):
|
class BaseTestConfig(TestCase):
|
||||||
|
@ -11,9 +13,36 @@ class BaseTestConfig(TestCase):
|
||||||
return app
|
return app
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
db.drop_all()
|
||||||
self.app = self.create_app().test_client()
|
self.app = self.create_app().test_client()
|
||||||
db.create_all()
|
db.create_all()
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
db.session.remove()
|
db.session.remove()
|
||||||
db.drop_all()
|
db.drop_all()
|
||||||
|
|
||||||
|
|
||||||
|
class BaseUserConfig(BaseTestConfig):
|
||||||
|
headers = {
|
||||||
|
"MsgSignature": message["sig"],
|
||||||
|
"RawTypedData": message["data"]
|
||||||
|
}
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
super(BaseUserConfig, self).setUp()
|
||||||
|
self.user = User.create(
|
||||||
|
account_address=test_user["accountAddress"],
|
||||||
|
email_address=test_user["emailAddress"],
|
||||||
|
display_name=test_user["displayName"],
|
||||||
|
title=test_user["title"],
|
||||||
|
_send_email=False
|
||||||
|
)
|
||||||
|
sm = SocialMedia(social_media_link=test_user['socialMedias'][0]['link'], user_id=self.user.id)
|
||||||
|
db.session.add(sm)
|
||||||
|
avatar = Avatar(image_url=test_user["avatar"]["link"], user_id=self.user.id)
|
||||||
|
db.session.add(avatar)
|
||||||
|
db.session.commit()
|
||||||
|
|
||||||
|
def remove_default_user(self):
|
||||||
|
User.query.filter_by(id=self.user.id).delete()
|
||||||
|
db.session.commit()
|
||||||
|
|
|
@ -1,81 +1,43 @@
|
||||||
import json
|
import json
|
||||||
import random
|
|
||||||
|
|
||||||
from grant.proposal.models import Proposal, CATEGORIES
|
from grant.proposal.models import Proposal
|
||||||
from grant.user.models import User, SocialMedia
|
from grant.user.models import SocialMedia, Avatar
|
||||||
from ..config import BaseTestConfig
|
from ..config import BaseUserConfig
|
||||||
|
from ..test_data import test_proposal
|
||||||
milestones = [
|
|
||||||
{
|
|
||||||
"title": "All the money straightaway",
|
|
||||||
"description": "cool stuff with it",
|
|
||||||
"date": "June 2019",
|
|
||||||
"payoutPercent": "100",
|
|
||||||
"immediatePayout": False
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
team = [
|
|
||||||
{
|
|
||||||
"accountAddress": "0x1",
|
|
||||||
"displayName": 'Groot',
|
|
||||||
"emailAddress": 'iam@groot.com',
|
|
||||||
"title": 'I am Groot!',
|
|
||||||
"avatar": {
|
|
||||||
"link": 'https://avatars2.githubusercontent.com/u/1393943?s=400&v=4'
|
|
||||||
},
|
|
||||||
"socialMedias": [
|
|
||||||
{
|
|
||||||
"link": 'https://github.com/groot'
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
proposal = {
|
|
||||||
"team": team,
|
|
||||||
"crowdFundContractAddress": "0x20000",
|
|
||||||
"content": "## My Proposal",
|
|
||||||
"title": "Give Me Money",
|
|
||||||
"milestones": milestones,
|
|
||||||
"category": random.choice(CATEGORIES)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
class TestAPI(BaseTestConfig):
|
class TestAPI(BaseUserConfig):
|
||||||
def test_create_new_proposal(self):
|
def test_create_new_proposal(self):
|
||||||
self.assertIsNone(Proposal.query.filter_by(
|
self.assertIsNone(Proposal.query.filter_by(
|
||||||
proposal_address=proposal["crowdFundContractAddress"]
|
proposal_address=test_proposal["crowdFundContractAddress"]
|
||||||
).first())
|
).first())
|
||||||
|
|
||||||
resp = self.app.post(
|
resp = self.app.post(
|
||||||
"/api/v1/proposals/",
|
"/api/v1/proposals/",
|
||||||
data=json.dumps(proposal),
|
data=json.dumps(test_proposal),
|
||||||
|
headers=self.headers,
|
||||||
content_type='application/json'
|
content_type='application/json'
|
||||||
)
|
)
|
||||||
|
self.assertEqual(resp.status_code, 201)
|
||||||
|
|
||||||
proposal_db = Proposal.query.filter_by(
|
proposal_db = Proposal.query.filter_by(
|
||||||
proposal_address=proposal["crowdFundContractAddress"]
|
proposal_address=test_proposal["crowdFundContractAddress"]
|
||||||
).first()
|
).first()
|
||||||
self.assertEqual(proposal_db.title, proposal["title"])
|
self.assertEqual(proposal_db.title, test_proposal["title"])
|
||||||
|
|
||||||
# User
|
|
||||||
user_db = User.query.filter_by(email_address=team[0]["emailAddress"]).first()
|
|
||||||
self.assertEqual(user_db.display_name, team[0]["displayName"])
|
|
||||||
self.assertEqual(user_db.title, team[0]["title"])
|
|
||||||
self.assertEqual(user_db.account_address, team[0]["accountAddress"])
|
|
||||||
|
|
||||||
# SocialMedia
|
# SocialMedia
|
||||||
social_media_db = SocialMedia.query.filter_by(social_media_link=team[0]["socialMedias"][0]["link"]).first()
|
social_media_db = SocialMedia.query.filter_by(user_id=self.user.id).first()
|
||||||
self.assertTrue(social_media_db)
|
self.assertTrue(social_media_db)
|
||||||
|
|
||||||
# Avatar
|
# Avatar
|
||||||
self.assertEqual(user_db.avatar.image_url, team[0]["avatar"]["link"])
|
avatar = Avatar.query.filter_by(user_id=self.user.id).first()
|
||||||
|
self.assertTrue(avatar)
|
||||||
|
|
||||||
def test_create_new_proposal_comment(self):
|
def test_create_new_proposal_comment(self):
|
||||||
proposal_res = self.app.post(
|
proposal_res = self.app.post(
|
||||||
"/api/v1/proposals/",
|
"/api/v1/proposals/",
|
||||||
data=json.dumps(proposal),
|
data=json.dumps(test_proposal),
|
||||||
|
headers=self.headers,
|
||||||
content_type='application/json'
|
content_type='application/json'
|
||||||
)
|
)
|
||||||
proposal_json = proposal_res.json
|
proposal_json = proposal_res.json
|
||||||
|
@ -94,15 +56,17 @@ class TestAPI(BaseTestConfig):
|
||||||
self.assertTrue(comment_res.json)
|
self.assertTrue(comment_res.json)
|
||||||
|
|
||||||
def test_create_new_proposal_duplicate(self):
|
def test_create_new_proposal_duplicate(self):
|
||||||
proposal_res = self.app.post(
|
self.app.post(
|
||||||
"/api/v1/proposals/",
|
"/api/v1/proposals/",
|
||||||
data=json.dumps(proposal),
|
data=json.dumps(test_proposal),
|
||||||
|
headers=self.headers,
|
||||||
content_type='application/json'
|
content_type='application/json'
|
||||||
)
|
)
|
||||||
|
|
||||||
proposal_res2 = self.app.post(
|
proposal_res2 = self.app.post(
|
||||||
"/api/v1/proposals/",
|
"/api/v1/proposals/",
|
||||||
data=json.dumps(proposal),
|
data=json.dumps(test_proposal),
|
||||||
|
headers=self.headers,
|
||||||
content_type='application/json'
|
content_type='application/json'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import json
|
import json
|
||||||
import random
|
import random
|
||||||
|
|
||||||
from grant.proposal.models import CATEGORIES
|
from grant.proposal.models import CATEGORIES
|
||||||
|
|
||||||
message = {
|
message = {
|
||||||
|
@ -43,7 +44,7 @@ message = {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
user = {
|
test_user = {
|
||||||
"accountAddress": '0xcd2a3d9f938e13cd947ec05abc7fe734df8dd826',
|
"accountAddress": '0xcd2a3d9f938e13cd947ec05abc7fe734df8dd826',
|
||||||
"displayName": 'Groot',
|
"displayName": 'Groot',
|
||||||
"emailAddress": 'iam@groot.com',
|
"emailAddress": 'iam@groot.com',
|
||||||
|
@ -60,7 +61,7 @@ user = {
|
||||||
"rawTypedData": json.dumps(message["data"])
|
"rawTypedData": json.dumps(message["data"])
|
||||||
}
|
}
|
||||||
|
|
||||||
team = [user]
|
test_team = [test_user]
|
||||||
|
|
||||||
milestones = [
|
milestones = [
|
||||||
{
|
{
|
||||||
|
@ -72,11 +73,21 @@ milestones = [
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
proposal = {
|
test_proposal = {
|
||||||
"team": team,
|
"team": test_team,
|
||||||
"crowdFundContractAddress": "0x20000",
|
"crowdFundContractAddress": "0x20000",
|
||||||
"content": "## My Proposal",
|
"content": "## My Proposal",
|
||||||
"title": "Give Me Money",
|
"title": "Give Me Money",
|
||||||
"milestones": milestones,
|
"milestones": milestones,
|
||||||
"category": random.choice(CATEGORIES)
|
"category": random.choice(CATEGORIES)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
milestones = [
|
||||||
|
{
|
||||||
|
"title": "All the money straightaway",
|
||||||
|
"description": "cool stuff with it",
|
||||||
|
"date": "June 2019",
|
||||||
|
"payoutPercent": "100",
|
||||||
|
"immediatePayout": False
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
|
@ -1,14 +1,14 @@
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from ..config import BaseTestConfig
|
from ..config import BaseTestConfig
|
||||||
from ..test_data import user, message
|
from ..test_data import test_user, message
|
||||||
|
|
||||||
|
|
||||||
class TestRequiredSignedMessageDecorator(BaseTestConfig):
|
class TestRequiredSignedMessageDecorator(BaseTestConfig):
|
||||||
def test_required_sm_aborts_without_data_and_sig_headers(self):
|
def test_required_sm_aborts_without_data_and_sig_headers(self):
|
||||||
self.app.post(
|
self.app.post(
|
||||||
"/api/v1/users/",
|
"/api/v1/users/",
|
||||||
data=json.dumps(user),
|
data=json.dumps(test_user),
|
||||||
content_type='application/json'
|
content_type='application/json'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -53,7 +53,7 @@ class TestRequiredSignedMessageDecorator(BaseTestConfig):
|
||||||
def test_required_sm_decorator_authorizes_when_recovered_address_matches_existing_user(self):
|
def test_required_sm_decorator_authorizes_when_recovered_address_matches_existing_user(self):
|
||||||
self.app.post(
|
self.app.post(
|
||||||
"/api/v1/users/",
|
"/api/v1/users/",
|
||||||
data=json.dumps(user),
|
data=json.dumps(test_user),
|
||||||
content_type='application/json'
|
content_type='application/json'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -68,4 +68,4 @@ class TestRequiredSignedMessageDecorator(BaseTestConfig):
|
||||||
response_json = response.json
|
response_json = response.json
|
||||||
|
|
||||||
self.assert200(response)
|
self.assert200(response)
|
||||||
self.assertEqual(response_json["displayName"], user["displayName"])
|
self.assertEqual(response_json["displayName"], test_user["displayName"])
|
||||||
|
|
|
@ -1,84 +1,101 @@
|
||||||
import copy
|
import copy
|
||||||
import json
|
import json
|
||||||
|
|
||||||
from grant.proposal.models import Proposal
|
from animal_case import animalify
|
||||||
from grant.user.models import User
|
|
||||||
from ..config import BaseTestConfig
|
|
||||||
from ..test_data import team, proposal
|
|
||||||
from mock import patch
|
from mock import patch
|
||||||
|
|
||||||
|
from grant.proposal.models import Proposal
|
||||||
|
from grant.user.models import User, user_schema
|
||||||
|
from ..config import BaseUserConfig
|
||||||
|
from ..test_data import test_team, test_proposal, test_user
|
||||||
|
|
||||||
class TestAPI(BaseTestConfig):
|
|
||||||
def test_create_new_user_via_proposal_by_account_address(self):
|
|
||||||
proposal_by_account = copy.deepcopy(proposal)
|
|
||||||
del proposal_by_account["team"][0]["emailAddress"]
|
|
||||||
|
|
||||||
self.app.post(
|
class TestAPI(BaseUserConfig):
|
||||||
"/api/v1/proposals/",
|
# TODO create second signed message default user
|
||||||
data=json.dumps(proposal_by_account),
|
# @patch('grant.email.send.send_email')
|
||||||
content_type='application/json'
|
# def test_create_new_user_via_proposal_by_account_address(self, mock_send_email):
|
||||||
)
|
# mock_send_email.return_value.ok = True
|
||||||
|
# self.remove_default_user()
|
||||||
|
# proposal_by_account = copy.deepcopy(test_proposal)
|
||||||
|
# del proposal_by_account["team"][0]["emailAddress"]
|
||||||
|
#
|
||||||
|
# resp = self.app.post(
|
||||||
|
# "/api/v1/proposals/",
|
||||||
|
# data=json.dumps(proposal_by_account),
|
||||||
|
# headers=self.headers,
|
||||||
|
# content_type='application/json'
|
||||||
|
# )
|
||||||
|
#
|
||||||
|
# self.assertEqual(resp, 201)
|
||||||
|
#
|
||||||
|
# # User
|
||||||
|
# user_db = User.query.filter_by(account_address=proposal_by_account["team"][0]["accountAddress"]).first()
|
||||||
|
# self.assertEqual(user_db.display_name, proposal_by_account["team"][0]["displayName"])
|
||||||
|
# self.assertEqual(user_db.title, proposal_by_account["team"][0]["title"])
|
||||||
|
# self.assertEqual(user_db.account_address, proposal_by_account["team"][0]["accountAddress"])
|
||||||
|
|
||||||
# User
|
# TODO create second signed message default user
|
||||||
user_db = User.query.filter_by(account_address=proposal_by_account["team"][0]["accountAddress"]).first()
|
# def test_create_new_user_via_proposal_by_email(self):
|
||||||
self.assertEqual(user_db.display_name, proposal_by_account["team"][0]["displayName"])
|
# self.remove_default_user()
|
||||||
self.assertEqual(user_db.title, proposal_by_account["team"][0]["title"])
|
# proposal_by_email = copy.deepcopy(test_proposal)
|
||||||
self.assertEqual(user_db.account_address, proposal_by_account["team"][0]["accountAddress"])
|
# del proposal_by_email["team"][0]["accountAddress"]
|
||||||
|
#
|
||||||
def test_create_new_user_via_proposal_by_email(self):
|
# resp = self.app.post(
|
||||||
proposal_by_email = copy.deepcopy(proposal)
|
# "/api/v1/proposals/",
|
||||||
del proposal_by_email["team"][0]["accountAddress"]
|
# data=json.dumps(proposal_by_email),
|
||||||
|
# headers=self.headers,
|
||||||
self.app.post(
|
# content_type='application/json'
|
||||||
"/api/v1/proposals/",
|
# )
|
||||||
data=json.dumps(proposal_by_email),
|
#
|
||||||
content_type='application/json'
|
# self.assertEqual(resp, 201)
|
||||||
)
|
#
|
||||||
|
# # User
|
||||||
# User
|
# user_db = User.query.filter_by(email_address=proposal_by_email["team"][0]["emailAddress"]).first()
|
||||||
user_db = User.query.filter_by(email_address=proposal_by_email["team"][0]["emailAddress"]).first()
|
# self.assertEqual(user_db.display_name, proposal_by_email["team"][0]["displayName"])
|
||||||
self.assertEqual(user_db.display_name, proposal_by_email["team"][0]["displayName"])
|
# self.assertEqual(user_db.title, proposal_by_email["team"][0]["title"])
|
||||||
self.assertEqual(user_db.title, proposal_by_email["team"][0]["title"])
|
|
||||||
|
|
||||||
def test_associate_user_via_proposal_by_email(self):
|
def test_associate_user_via_proposal_by_email(self):
|
||||||
proposal_by_email = copy.deepcopy(proposal)
|
proposal_by_email = copy.deepcopy(test_proposal)
|
||||||
del proposal_by_email["team"][0]["accountAddress"]
|
del proposal_by_email["team"][0]["accountAddress"]
|
||||||
|
|
||||||
self.app.post(
|
resp = self.app.post(
|
||||||
"/api/v1/proposals/",
|
"/api/v1/proposals/",
|
||||||
data=json.dumps(proposal_by_email),
|
data=json.dumps(proposal_by_email),
|
||||||
|
headers=self.headers,
|
||||||
content_type='application/json'
|
content_type='application/json'
|
||||||
)
|
)
|
||||||
|
self.assertEqual(resp.status_code, 201)
|
||||||
|
|
||||||
# User
|
# User
|
||||||
user_db = User.query.filter_by(email_address=proposal_by_email["team"][0]["emailAddress"]).first()
|
user_db = User.query.filter_by(email_address=proposal_by_email["team"][0]["emailAddress"]).first()
|
||||||
self.assertEqual(user_db.display_name, proposal_by_email["team"][0]["displayName"])
|
self.assertEqual(user_db.display_name, proposal_by_email["team"][0]["displayName"])
|
||||||
self.assertEqual(user_db.title, proposal_by_email["team"][0]["title"])
|
self.assertEqual(user_db.title, proposal_by_email["team"][0]["title"])
|
||||||
proposal_db = Proposal.query.filter_by(
|
proposal_db = Proposal.query.filter_by(
|
||||||
proposal_address=proposal["crowdFundContractAddress"]
|
proposal_address=test_proposal["crowdFundContractAddress"]
|
||||||
).first()
|
).first()
|
||||||
self.assertEqual(proposal_db.team[0].id, user_db.id)
|
self.assertEqual(proposal_db.team[0].id, user_db.id)
|
||||||
|
|
||||||
def test_associate_user_via_proposal_by_email_when_user_already_exists(self):
|
def test_associate_user_via_proposal_by_email_when_user_already_exists(self):
|
||||||
proposal_by_email = copy.deepcopy(proposal)
|
proposal_by_user_email = copy.deepcopy(test_proposal)
|
||||||
del proposal_by_email["team"][0]["accountAddress"]
|
del proposal_by_user_email["team"][0]["accountAddress"]
|
||||||
|
|
||||||
self.app.post(
|
resp = self.app.post(
|
||||||
"/api/v1/proposals/",
|
"/api/v1/proposals/",
|
||||||
data=json.dumps(proposal_by_email),
|
data=json.dumps(proposal_by_user_email),
|
||||||
|
headers=self.headers,
|
||||||
content_type='application/json'
|
content_type='application/json'
|
||||||
)
|
)
|
||||||
|
self.assertEqual(resp.status_code, 201)
|
||||||
|
|
||||||
# User
|
# User
|
||||||
user_db = User.query.filter_by(email_address=proposal_by_email["team"][0]["emailAddress"]).first()
|
self.assertEqual(self.user.display_name, proposal_by_user_email["team"][0]["displayName"])
|
||||||
self.assertEqual(user_db.display_name, proposal_by_email["team"][0]["displayName"])
|
self.assertEqual(self.user.title, proposal_by_user_email["team"][0]["title"])
|
||||||
self.assertEqual(user_db.title, proposal_by_email["team"][0]["title"])
|
|
||||||
proposal_db = Proposal.query.filter_by(
|
proposal_db = Proposal.query.filter_by(
|
||||||
proposal_address=proposal["crowdFundContractAddress"]
|
proposal_address=test_proposal["crowdFundContractAddress"]
|
||||||
).first()
|
).first()
|
||||||
self.assertEqual(proposal_db.team[0].id, user_db.id)
|
self.assertEqual(proposal_db.team[0].id, self.user.id)
|
||||||
|
|
||||||
new_proposal_by_email = copy.deepcopy(proposal)
|
new_proposal_by_email = copy.deepcopy(test_proposal)
|
||||||
new_proposal_by_email["crowdFundContractAddress"] = "0x2222"
|
new_proposal_by_email["crowdFundContractAddress"] = "0x2222"
|
||||||
del new_proposal_by_email["team"][0]["accountAddress"]
|
del new_proposal_by_email["team"][0]["accountAddress"]
|
||||||
|
|
||||||
|
@ -92,14 +109,14 @@ class TestAPI(BaseTestConfig):
|
||||||
self.assertEqual(user_db.display_name, new_proposal_by_email["team"][0]["displayName"])
|
self.assertEqual(user_db.display_name, new_proposal_by_email["team"][0]["displayName"])
|
||||||
self.assertEqual(user_db.title, new_proposal_by_email["team"][0]["title"])
|
self.assertEqual(user_db.title, new_proposal_by_email["team"][0]["title"])
|
||||||
proposal_db = Proposal.query.filter_by(
|
proposal_db = Proposal.query.filter_by(
|
||||||
proposal_address=proposal["crowdFundContractAddress"]
|
proposal_address=test_proposal["crowdFundContractAddress"]
|
||||||
).first()
|
).first()
|
||||||
self.assertEqual(proposal_db.team[0].id, user_db.id)
|
self.assertEqual(proposal_db.team[0].id, user_db.id)
|
||||||
|
|
||||||
def test_get_all_users(self):
|
def test_get_all_users(self):
|
||||||
self.app.post(
|
self.app.post(
|
||||||
"/api/v1/proposals/",
|
"/api/v1/proposals/",
|
||||||
data=json.dumps(proposal),
|
data=json.dumps(test_proposal),
|
||||||
content_type='application/json'
|
content_type='application/json'
|
||||||
)
|
)
|
||||||
users_get_resp = self.app.get(
|
users_get_resp = self.app.get(
|
||||||
|
@ -107,18 +124,17 @@ class TestAPI(BaseTestConfig):
|
||||||
)
|
)
|
||||||
|
|
||||||
users_json = users_get_resp.json
|
users_json = users_get_resp.json
|
||||||
print(users_json)
|
self.assertEqual(users_json[0]["displayName"], test_team[0]["displayName"])
|
||||||
self.assertEqual(users_json[0]["displayName"], team[0]["displayName"])
|
|
||||||
|
|
||||||
def test_get_user_associated_with_proposal(self):
|
def test_get_user_associated_with_proposal(self):
|
||||||
self.app.post(
|
self.app.post(
|
||||||
"/api/v1/proposals/",
|
"/api/v1/proposals/",
|
||||||
data=json.dumps(proposal),
|
data=json.dumps(test_proposal),
|
||||||
content_type='application/json'
|
content_type='application/json'
|
||||||
)
|
)
|
||||||
|
|
||||||
data = {
|
data = {
|
||||||
'proposalId': proposal["crowdFundContractAddress"]
|
'proposalId': test_proposal["crowdFundContractAddress"]
|
||||||
}
|
}
|
||||||
|
|
||||||
users_get_resp = self.app.get(
|
users_get_resp = self.app.get(
|
||||||
|
@ -127,25 +143,25 @@ class TestAPI(BaseTestConfig):
|
||||||
)
|
)
|
||||||
|
|
||||||
users_json = users_get_resp.json
|
users_json = users_get_resp.json
|
||||||
self.assertEqual(users_json[0]["avatar"]["imageUrl"], team[0]["avatar"]["link"])
|
self.assertEqual(users_json[0]["avatar"]["imageUrl"], test_team[0]["avatar"]["link"])
|
||||||
self.assertEqual(users_json[0]["socialMedias"][0]["socialMediaLink"], team[0]["socialMedias"][0]["link"])
|
self.assertEqual(users_json[0]["socialMedias"][0]["socialMediaLink"], test_team[0]["socialMedias"][0]["link"])
|
||||||
self.assertEqual(users_json[0]["displayName"], team[0]["displayName"])
|
self.assertEqual(users_json[0]["displayName"], test_user["displayName"])
|
||||||
|
|
||||||
def test_get_single_user(self):
|
def test_get_single_user(self):
|
||||||
self.app.post(
|
self.app.post(
|
||||||
"/api/v1/proposals/",
|
"/api/v1/proposals/",
|
||||||
data=json.dumps(proposal),
|
data=json.dumps(test_proposal),
|
||||||
content_type='application/json'
|
content_type='application/json'
|
||||||
)
|
)
|
||||||
|
|
||||||
users_get_resp = self.app.get(
|
users_get_resp = self.app.get(
|
||||||
"/api/v1/users/{}".format(proposal["team"][0]["emailAddress"])
|
"/api/v1/users/{}".format(test_proposal["team"][0]["emailAddress"])
|
||||||
)
|
)
|
||||||
|
|
||||||
users_json = users_get_resp.json
|
users_json = users_get_resp.json
|
||||||
self.assertEqual(users_json["avatar"]["imageUrl"], team[0]["avatar"]["link"])
|
self.assertEqual(users_json["avatar"]["imageUrl"], test_team[0]["avatar"]["link"])
|
||||||
self.assertEqual(users_json["socialMedias"][0]["socialMediaLink"], team[0]["socialMedias"][0]["link"])
|
self.assertEqual(users_json["socialMedias"][0]["socialMediaLink"], test_team[0]["socialMedias"][0]["link"])
|
||||||
self.assertEqual(users_json["displayName"], team[0]["displayName"])
|
self.assertEqual(users_json["displayName"], test_team[0]["displayName"])
|
||||||
|
|
||||||
@patch('grant.email.send.send_email')
|
@patch('grant.email.send.send_email')
|
||||||
def test_create_user(self, mock_send_email):
|
def test_create_user(self, mock_send_email):
|
||||||
|
@ -153,15 +169,15 @@ class TestAPI(BaseTestConfig):
|
||||||
|
|
||||||
self.app.post(
|
self.app.post(
|
||||||
"/api/v1/users/",
|
"/api/v1/users/",
|
||||||
data=json.dumps(team[0]),
|
data=json.dumps(test_team[0]),
|
||||||
content_type='application/json'
|
content_type='application/json'
|
||||||
)
|
)
|
||||||
|
|
||||||
# User
|
# User
|
||||||
user_db = User.get_by_email_or_account_address(account_address=team[0]["accountAddress"])
|
user_db = User.get_by_identifier(account_address=test_team[0]["accountAddress"])
|
||||||
self.assertEqual(user_db.display_name, team[0]["displayName"])
|
self.assertEqual(user_db.display_name, test_team[0]["displayName"])
|
||||||
self.assertEqual(user_db.title, team[0]["title"])
|
self.assertEqual(user_db.title, test_team[0]["title"])
|
||||||
self.assertEqual(user_db.account_address, team[0]["accountAddress"])
|
self.assertEqual(user_db.account_address, test_team[0]["accountAddress"])
|
||||||
|
|
||||||
@patch('grant.email.send.send_email')
|
@patch('grant.email.send.send_email')
|
||||||
def test_create_user_duplicate_400(self, mock_send_email):
|
def test_create_user_duplicate_400(self, mock_send_email):
|
||||||
|
@ -170,64 +186,28 @@ class TestAPI(BaseTestConfig):
|
||||||
|
|
||||||
response = self.app.post(
|
response = self.app.post(
|
||||||
"/api/v1/users/",
|
"/api/v1/users/",
|
||||||
data=json.dumps(team[0]),
|
data=json.dumps(test_team[0]),
|
||||||
content_type='application/json'
|
content_type='application/json'
|
||||||
)
|
)
|
||||||
|
|
||||||
self.assertEqual(response.status_code, 409)
|
self.assertEqual(response.status_code, 409)
|
||||||
|
|
||||||
def test_update_user_remove_social_and_avatar(self):
|
def test_update_user_remove_social_and_avatar(self):
|
||||||
self.app.post(
|
updated_user = animalify(copy.deepcopy(user_schema.dump(self.user)))
|
||||||
"/api/v1/proposals/",
|
updated_user["displayName"] = 'new display name'
|
||||||
data=json.dumps(proposal),
|
updated_user["avatar"] = None
|
||||||
content_type='application/json'
|
updated_user["socialMedias"] = None
|
||||||
)
|
|
||||||
|
|
||||||
updated_user = copy.deepcopy(team[0])
|
|
||||||
updated_user['displayName'] = 'Billy'
|
|
||||||
updated_user['title'] = 'Commander'
|
|
||||||
updated_user['socialMedias'] = []
|
|
||||||
updated_user['avatar'] = {}
|
|
||||||
|
|
||||||
user_update_resp = self.app.put(
|
user_update_resp = self.app.put(
|
||||||
"/api/v1/users/{}".format(proposal["team"][0]["accountAddress"]),
|
"/api/v1/users/{}".format(self.user.account_address),
|
||||||
data=json.dumps(updated_user),
|
data=json.dumps(updated_user),
|
||||||
|
headers=self.headers,
|
||||||
content_type='application/json'
|
content_type='application/json'
|
||||||
)
|
)
|
||||||
|
self.assert200(user_update_resp)
|
||||||
|
|
||||||
users_json = user_update_resp.json
|
user_json = user_update_resp.json
|
||||||
self.assertFalse(users_json["avatar"])
|
self.assertFalse(user_json["avatar"])
|
||||||
self.assertFalse(len(users_json["socialMedias"]))
|
self.assertFalse(len(user_json["socialMedias"]))
|
||||||
self.assertEqual(users_json["displayName"], updated_user["displayName"])
|
self.assertEqual(user_json["displayName"], updated_user["displayName"])
|
||||||
self.assertEqual(users_json["title"], updated_user["title"])
|
self.assertEqual(user_json["title"], updated_user["title"])
|
||||||
|
|
||||||
def test_update_user(self):
|
|
||||||
self.app.post(
|
|
||||||
"/api/v1/proposals/",
|
|
||||||
data=json.dumps(proposal),
|
|
||||||
content_type='application/json'
|
|
||||||
)
|
|
||||||
|
|
||||||
updated_user = copy.deepcopy(team[0])
|
|
||||||
updated_user['displayName'] = 'Billy'
|
|
||||||
updated_user['title'] = 'Commander'
|
|
||||||
updated_user['socialMedias'] = [
|
|
||||||
{
|
|
||||||
"link": "https://github.com/billyman"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
updated_user['avatar'] = {
|
|
||||||
"link": "https://x.io/avatar.png"
|
|
||||||
}
|
|
||||||
|
|
||||||
user_update_resp = self.app.put(
|
|
||||||
"/api/v1/users/{}".format(proposal["team"][0]["accountAddress"]),
|
|
||||||
data=json.dumps(updated_user),
|
|
||||||
content_type='application/json'
|
|
||||||
)
|
|
||||||
|
|
||||||
users_json = user_update_resp.json
|
|
||||||
self.assertEqual(users_json["avatar"]["imageUrl"], updated_user["avatar"]["link"])
|
|
||||||
self.assertEqual(users_json["socialMedias"][0]["socialMediaLink"], updated_user["socialMedias"][0]["link"])
|
|
||||||
self.assertEqual(users_json["displayName"], updated_user["displayName"])
|
|
||||||
self.assertEqual(users_json["title"], updated_user["title"])
|
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { composeWithDevTools } from 'redux-devtools-extension';
|
||||||
import { persistStore, Persistor } from 'redux-persist';
|
import { persistStore, Persistor } from 'redux-persist';
|
||||||
import rootReducer, { AppState, combineInitialState } from './reducers';
|
import rootReducer, { AppState, combineInitialState } from './reducers';
|
||||||
import rootSaga from './sagas';
|
import rootSaga from './sagas';
|
||||||
|
import axios from 'api/axios';
|
||||||
|
|
||||||
const sagaMiddleware = createSagaMiddleware();
|
const sagaMiddleware = createSagaMiddleware();
|
||||||
|
|
||||||
|
@ -43,5 +44,24 @@ export function configureStore(initialState: Partial<AppState> = combineInitialS
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Any global listeners to the store go here
|
||||||
|
let prevState = store.getState();
|
||||||
|
store.subscribe(() => {
|
||||||
|
const state = store.getState();
|
||||||
|
|
||||||
|
// Setup the API with auth credentials whenever they change
|
||||||
|
const { authSignature } = state.auth;
|
||||||
|
if (authSignature !== prevState.auth.authSignature) {
|
||||||
|
axios.defaults.headers.common.MsgSignature = authSignature
|
||||||
|
? authSignature.signedMessage
|
||||||
|
: undefined;
|
||||||
|
axios.defaults.headers.common.RawTypedData = authSignature
|
||||||
|
? JSON.stringify(authSignature.rawTypedData)
|
||||||
|
: undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
prevState = state;
|
||||||
|
});
|
||||||
|
|
||||||
return { store, persistor };
|
return { store, persistor };
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue