Merge branch 'auth-endpoints' into working-comments
This commit is contained in:
commit
56df655c05
|
@ -33,7 +33,7 @@ class ProposalsNaked extends React.Component<Props> {
|
|||
}
|
||||
|
||||
render() {
|
||||
const id = this.props.match.params.id;
|
||||
const id = Number(this.props.match.params.id);
|
||||
const { proposals, proposalsFetched } = store;
|
||||
|
||||
if (!proposalsFetched) {
|
||||
|
@ -118,7 +118,8 @@ class ProposalItemNaked extends React.Component<Proposal> {
|
|||
disabled={true}
|
||||
/>
|
||||
</div>
|
||||
<b>{p.title}</b> {p.proposalId} <Field title="category" value={p.category} />
|
||||
<b>{p.title}</b> [{p.proposalId}]{p.proposalAddress}{' '}
|
||||
<Field title="category" value={p.category} />
|
||||
<Field title="dateCreated" value={p.dateCreated * 1000} isTime={true} />
|
||||
<Field title="stage" value={p.stage} />
|
||||
<Field
|
||||
|
@ -230,7 +231,7 @@ const ProposalItem = view(ProposalItemNaked);
|
|||
|
||||
// tslint:disable-next-line:max-classes-per-file
|
||||
class ContractMethodNaked extends React.Component<
|
||||
TContractMethod & { proposalId: string; name: string }
|
||||
TContractMethod & { proposalId: number; name: string }
|
||||
> {
|
||||
state = {};
|
||||
render() {
|
||||
|
@ -324,7 +325,7 @@ const ContractMethod = view(ContractMethodNaked);
|
|||
|
||||
// tslint:disable-next-line:max-classes-per-file
|
||||
class ContractMethodSendNaked extends React.Component<
|
||||
TContractMethod & { proposalId: string; name: string }
|
||||
TContractMethod & { proposalId: number; name: string }
|
||||
> {
|
||||
state = {
|
||||
args: this.props.input.map(i => (i.type === 'boolean' ? false : '')) as any[],
|
||||
|
|
|
@ -54,7 +54,7 @@ async function fetchProposals() {
|
|||
return data;
|
||||
}
|
||||
|
||||
async function deleteProposal(id: string) {
|
||||
async function deleteProposal(id: number) {
|
||||
const { data } = await api.delete('/admin/proposals/' + id);
|
||||
return data;
|
||||
}
|
||||
|
@ -144,14 +144,15 @@ const app = store({
|
|||
}
|
||||
},
|
||||
|
||||
async populateProposalContract(proposalId: string) {
|
||||
async populateProposalContract(proposalId: number) {
|
||||
console.log(proposalId);
|
||||
if (web3) {
|
||||
await populateProposalContract(app, web3, proposalId);
|
||||
}
|
||||
},
|
||||
|
||||
async proposalContractSend(
|
||||
proposalId: string,
|
||||
proposalId: number,
|
||||
methodName: keyof Contract,
|
||||
inputs: ContractMethodInput[],
|
||||
args: any[],
|
||||
|
@ -161,7 +162,7 @@ const app = store({
|
|||
}
|
||||
},
|
||||
|
||||
async deleteProposal(id: string) {
|
||||
async deleteProposal(id: number) {
|
||||
try {
|
||||
await deleteProposal(id);
|
||||
app.proposals = app.proposals.filter(p => p.proposalId === id);
|
||||
|
|
|
@ -13,7 +13,8 @@ export interface Milestone {
|
|||
title: string;
|
||||
}
|
||||
export interface Proposal {
|
||||
proposalId: string;
|
||||
proposalId: number;
|
||||
proposalAddress: string;
|
||||
dateCreated: number;
|
||||
title: string;
|
||||
body: string;
|
||||
|
|
|
@ -71,16 +71,17 @@ function checkCrowdFundFactory(app: TApp, web3: Web3) {
|
|||
export async function proposalContractSend(
|
||||
app: TApp,
|
||||
web3: Web3,
|
||||
proposalId: string,
|
||||
proposalId: number,
|
||||
methodName: keyof Contract,
|
||||
inputs: ContractMethodInput[],
|
||||
args: any[],
|
||||
) {
|
||||
const storeProposal = app.proposals.find(p => p.proposalId === proposalId);
|
||||
if (storeProposal) {
|
||||
const { proposalAddress } = storeProposal;
|
||||
await getAccount(app, web3);
|
||||
const storeMethod = storeProposal.contract[methodName];
|
||||
const contract = new web3.eth.Contract(CrowdFund.abi, proposalId);
|
||||
const contract = new web3.eth.Contract(CrowdFund.abi, proposalAddress);
|
||||
app.crowdFundGeneralStatus = `calling (${storeProposal.title}).${methodName}...`;
|
||||
try {
|
||||
console.log(args);
|
||||
|
@ -120,12 +121,13 @@ export async function proposalContractSend(
|
|||
export async function populateProposalContract(
|
||||
app: TApp,
|
||||
web3: Web3,
|
||||
proposalId: string,
|
||||
proposalId: number,
|
||||
) {
|
||||
const storeProposal = app.proposals.find(p => p.proposalId === proposalId);
|
||||
const contract = new web3.eth.Contract(CrowdFund.abi, proposalId);
|
||||
|
||||
if (storeProposal) {
|
||||
const { proposalAddress } = storeProposal;
|
||||
const contract = new web3.eth.Contract(CrowdFund.abi, proposalAddress);
|
||||
storeProposal.contractStatus = 'loading...';
|
||||
const methods = Object.keys(INITIAL_CONTRACT).map(k => k as keyof Contract);
|
||||
for (const method of methods) {
|
||||
|
@ -137,7 +139,7 @@ export async function populateProposalContract(
|
|||
try {
|
||||
storeMethod.status = 'loading';
|
||||
if (methodType === 'eth' && method === 'getBalance') {
|
||||
storeMethod.value = (await web3.eth.getBalance(proposalId)) + '';
|
||||
storeMethod.value = (await web3.eth.getBalance(proposalAddress)) + '';
|
||||
} else if (methodType === 'array') {
|
||||
const result = await collectArrayElements(contractMethod, app.ethAccount);
|
||||
if (method === 'milestones') {
|
||||
|
|
|
@ -102,8 +102,8 @@ def get_proposals(stage):
|
|||
if stage:
|
||||
proposals = (
|
||||
Proposal.query.filter_by(stage=stage)
|
||||
.order_by(Proposal.date_created.desc())
|
||||
.all()
|
||||
.order_by(Proposal.date_created.desc())
|
||||
.all()
|
||||
)
|
||||
else:
|
||||
proposals = Proposal.query.order_by(Proposal.date_created.desc()).all()
|
||||
|
@ -122,7 +122,6 @@ def get_proposals(stage):
|
|||
parameter('team', type=list, required=True)
|
||||
)
|
||||
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()
|
||||
if existing_proposal:
|
||||
return {"message": "Oops! Something went wrong."}, 409
|
||||
|
@ -219,6 +218,7 @@ def get_proposal_update(proposal_id, update_id):
|
|||
|
||||
@blueprint.route("/<proposal_id>/updates", methods=["POST"])
|
||||
@requires_team_member_auth
|
||||
@requires_sm
|
||||
@endpoint.api(
|
||||
parameter('title', type=str, required=True),
|
||||
parameter('content', type=str, required=True)
|
||||
|
|
|
@ -58,7 +58,7 @@ class User(db.Model):
|
|||
self.title = title
|
||||
|
||||
@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(
|
||||
account_address=account_address,
|
||||
email_address=email_address,
|
||||
|
@ -73,10 +73,11 @@ class User(db.Model):
|
|||
db.session.add(ev)
|
||||
db.session.commit()
|
||||
|
||||
send_email(user.email_address, 'signup', {
|
||||
'display_name': user.display_name,
|
||||
'confirm_url': make_url(f'/email/verify?code={ev.code}')
|
||||
})
|
||||
if send_email:
|
||||
send_email(user.email_address, 'signup', {
|
||||
'display_name': user.display_name,
|
||||
'confirm_url': make_url(f'/email/verify?code={ev.code}')
|
||||
})
|
||||
|
||||
return user
|
||||
|
||||
|
|
|
@ -1,10 +1,9 @@
|
|||
from flask import Blueprint, g
|
||||
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.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')
|
||||
|
||||
|
@ -54,12 +53,12 @@ def get_user(user_identity):
|
|||
parameter('rawTypedData', type=str, required=True)
|
||||
)
|
||||
def create_user(
|
||||
account_address,
|
||||
email_address,
|
||||
display_name,
|
||||
title,
|
||||
signed_message,
|
||||
raw_typed_data
|
||||
account_address,
|
||||
email_address,
|
||||
display_name,
|
||||
title,
|
||||
signed_message,
|
||||
raw_typed_data
|
||||
):
|
||||
existing_user = User.get_by_identifier(email_address=email_address, account_address=account_address)
|
||||
if existing_user:
|
||||
|
@ -70,11 +69,11 @@ def create_user(
|
|||
sig_address = verify_signed_auth(signed_message, raw_typed_data)
|
||||
if sig_address.lower() != account_address.lower():
|
||||
return {
|
||||
"message": "Message signature address ({sig_address}) doesn't match account_address ({account_address})".format(
|
||||
sig_address=sig_address,
|
||||
account_address=account_address
|
||||
)
|
||||
}, 400
|
||||
"message": "Message signature address ({sig_address}) doesn't match account_address ({account_address})".format(
|
||||
sig_address=sig_address,
|
||||
account_address=account_address
|
||||
)
|
||||
}, 400
|
||||
except BadSignatureException:
|
||||
return {"message": "Invalid message signature"}, 400
|
||||
|
||||
|
@ -88,6 +87,7 @@ def create_user(
|
|||
result = user_schema.dump(user)
|
||||
return result
|
||||
|
||||
|
||||
@blueprint.route("/auth", methods=["POST"])
|
||||
@endpoint.api(
|
||||
parameter('accountAddress', type=str, required=True),
|
||||
|
@ -103,31 +103,28 @@ def auth_user(account_address, signed_message, raw_typed_data):
|
|||
sig_address = verify_signed_auth(signed_message, raw_typed_data)
|
||||
if sig_address.lower() != account_address.lower():
|
||||
return {
|
||||
"message": "Message signature address ({sig_address}) doesn't match account_address ({account_address})".format(
|
||||
sig_address=sig_address,
|
||||
account_address=account_address
|
||||
)
|
||||
}, 400
|
||||
"message": "Message signature address ({sig_address}) doesn't match account_address ({account_address})".format(
|
||||
sig_address=sig_address,
|
||||
account_address=account_address
|
||||
)
|
||||
}, 400
|
||||
except BadSignatureException:
|
||||
return {"message": "Invalid message signature"}, 400
|
||||
|
||||
|
||||
return user_schema.dump(existing_user)
|
||||
|
||||
|
||||
@blueprint.route("/<user_identity>", methods=["PUT"])
|
||||
@requires_sm
|
||||
@requires_same_user_auth
|
||||
@endpoint.api(
|
||||
parameter('displayName', type=str, required=False),
|
||||
parameter('title', type=str, required=False),
|
||||
parameter('socialMedias', type=list, required=False),
|
||||
parameter('avatar', type=dict, required=False),
|
||||
parameter('displayName', type=str, required=True),
|
||||
parameter('title', type=str, required=True),
|
||||
parameter('socialMedias', type=list, required=True),
|
||||
parameter('avatar', type=dict, required=True)
|
||||
)
|
||||
def update_user(user_identity, display_name, title, social_medias, avatar):
|
||||
user = User.get_by_identifier(email_address=user_identity, account_address=user_identity)
|
||||
if not user:
|
||||
return {"message": "User with that address or email not found"}, 404
|
||||
|
||||
if user.id != g.current_user.id:
|
||||
return {"message": "You are not authorized to edit this user"}, 403
|
||||
user = g.current_user
|
||||
|
||||
if display_name is not None:
|
||||
user.display_name = display_name
|
||||
|
@ -136,11 +133,12 @@ def update_user(user_identity, display_name, title, social_medias, avatar):
|
|||
user.title = title
|
||||
|
||||
if social_medias is not None:
|
||||
sm_query = SocialMedia.query.filter_by(user_id=user.id)
|
||||
sm_query.delete()
|
||||
SocialMedia.query.filter_by(user_id=user.id).delete()
|
||||
for social_media in social_medias:
|
||||
sm = SocialMedia(social_media_link=social_media.get("link"), user_id=user.id)
|
||||
db.session.add(sm)
|
||||
else:
|
||||
SocialMedia.query.filter_by(user_id=user.id).delete()
|
||||
|
||||
if avatar is not None:
|
||||
Avatar.query.filter_by(user_id=user.id).delete()
|
||||
|
@ -148,8 +146,9 @@ def update_user(user_identity, display_name, title, social_medias, avatar):
|
|||
if avatar_link:
|
||||
avatar_obj = Avatar(image_url=avatar_link, user_id=user.id)
|
||||
db.session.add(avatar_obj)
|
||||
else:
|
||||
Avatar.query.filter_by(user_id=user.id).delete()
|
||||
|
||||
db.session.commit()
|
||||
|
||||
result = user_schema.dump(user)
|
||||
return result
|
||||
|
|
|
@ -8,8 +8,8 @@ from itsdangerous import SignatureExpired, BadSignature
|
|||
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer
|
||||
|
||||
from grant.settings import SECRET_KEY, AUTH_URL
|
||||
from ..user.models import User
|
||||
from ..proposal.models import Proposal
|
||||
from ..user.models import User
|
||||
|
||||
TWO_WEEKS = 1209600
|
||||
|
||||
|
@ -36,6 +36,7 @@ def verify_token(token):
|
|||
class BadSignatureException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
def verify_signed_auth(signature, typed_data):
|
||||
loaded_typed_data = ast.literal_eval(typed_data)
|
||||
if loaded_typed_data['domain']['name'] != 'Grant.io':
|
||||
|
@ -47,27 +48,10 @@ def verify_signed_auth(signature, typed_data):
|
|||
response = requests.request("POST", url, data=payload, headers=headers)
|
||||
json_response = response.json()
|
||||
recovered_address = json_response.get('recoveredAddress')
|
||||
|
||||
if not recovered_address:
|
||||
raise BadSignatureException("Authorization signature is invalid")
|
||||
|
||||
return recovered_address
|
||||
|
||||
|
||||
def requires_auth(f):
|
||||
@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
|
||||
|
||||
|
||||
# Decorator that requires you to have EIP-712 message signature headers for auth
|
||||
|
@ -78,7 +62,6 @@ def requires_sm(f):
|
|||
typed_data = request.headers.get('RawTypedData', None)
|
||||
|
||||
if typed_data and signature:
|
||||
auth_address = None
|
||||
try:
|
||||
auth_address = verify_signed_auth(signature, typed_data)
|
||||
except BadSignatureException:
|
||||
|
@ -95,6 +78,7 @@ def requires_sm(f):
|
|||
|
||||
return decorated
|
||||
|
||||
|
||||
# Decorator that requires you to be the user you're interacting with
|
||||
def requires_same_user_auth(f):
|
||||
@wraps(f)
|
||||
|
@ -104,13 +88,14 @@ def requires_same_user_auth(f):
|
|||
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 != g.current_user:
|
||||
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)
|
||||
|
@ -128,5 +113,5 @@ def requires_team_member_auth(f):
|
|||
|
||||
g.current_proposal = proposal
|
||||
return f(*args, **kwargs)
|
||||
|
||||
return requires_sm(decorated)
|
||||
|
||||
return requires_sm(decorated)
|
||||
|
|
|
@ -55,4 +55,4 @@ flask-sendgrid==0.6
|
|||
sendgrid==5.3.0
|
||||
|
||||
# input validation
|
||||
flask-yolo2API
|
||||
flask-yolo2API==0.2.4
|
|
@ -1,6 +1,8 @@
|
|||
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):
|
||||
|
@ -11,9 +13,36 @@ class BaseTestConfig(TestCase):
|
|||
return app
|
||||
|
||||
def setUp(self):
|
||||
db.drop_all()
|
||||
self.app = self.create_app().test_client()
|
||||
db.create_all()
|
||||
|
||||
def tearDown(self):
|
||||
db.session.remove()
|
||||
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 random
|
||||
|
||||
from grant.proposal.models import Proposal, CATEGORIES
|
||||
from grant.user.models import User, SocialMedia
|
||||
from ..config import BaseTestConfig
|
||||
|
||||
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)
|
||||
}
|
||||
from grant.proposal.models import Proposal
|
||||
from grant.user.models import SocialMedia, Avatar
|
||||
from ..config import BaseUserConfig
|
||||
from ..test_data import test_proposal
|
||||
|
||||
|
||||
class TestAPI(BaseTestConfig):
|
||||
class TestAPI(BaseUserConfig):
|
||||
def test_create_new_proposal(self):
|
||||
self.assertIsNone(Proposal.query.filter_by(
|
||||
proposal_address=proposal["crowdFundContractAddress"]
|
||||
proposal_address=test_proposal["crowdFundContractAddress"]
|
||||
).first())
|
||||
|
||||
resp = self.app.post(
|
||||
"/api/v1/proposals/",
|
||||
data=json.dumps(proposal),
|
||||
data=json.dumps(test_proposal),
|
||||
headers=self.headers,
|
||||
content_type='application/json'
|
||||
)
|
||||
self.assertEqual(resp.status_code, 201)
|
||||
|
||||
proposal_db = Proposal.query.filter_by(
|
||||
proposal_address=proposal["crowdFundContractAddress"]
|
||||
proposal_address=test_proposal["crowdFundContractAddress"]
|
||||
).first()
|
||||
self.assertEqual(proposal_db.title, 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"])
|
||||
self.assertEqual(proposal_db.title, test_proposal["title"])
|
||||
|
||||
# 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)
|
||||
|
||||
# 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):
|
||||
proposal_res = self.app.post(
|
||||
"/api/v1/proposals/",
|
||||
data=json.dumps(proposal),
|
||||
data=json.dumps(test_proposal),
|
||||
headers=self.headers,
|
||||
content_type='application/json'
|
||||
)
|
||||
proposal_json = proposal_res.json
|
||||
|
@ -94,15 +56,17 @@ class TestAPI(BaseTestConfig):
|
|||
self.assertTrue(comment_res.json)
|
||||
|
||||
def test_create_new_proposal_duplicate(self):
|
||||
proposal_res = self.app.post(
|
||||
self.app.post(
|
||||
"/api/v1/proposals/",
|
||||
data=json.dumps(proposal),
|
||||
data=json.dumps(test_proposal),
|
||||
headers=self.headers,
|
||||
content_type='application/json'
|
||||
)
|
||||
|
||||
proposal_res2 = self.app.post(
|
||||
"/api/v1/proposals/",
|
||||
data=json.dumps(proposal),
|
||||
data=json.dumps(test_proposal),
|
||||
headers=self.headers,
|
||||
content_type='application/json'
|
||||
)
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import json
|
||||
import random
|
||||
|
||||
from grant.proposal.models import CATEGORIES
|
||||
|
||||
message = {
|
||||
|
@ -43,7 +44,7 @@ message = {
|
|||
}
|
||||
}
|
||||
|
||||
user = {
|
||||
test_user = {
|
||||
"accountAddress": '0xcd2a3d9f938e13cd947ec05abc7fe734df8dd826',
|
||||
"displayName": 'Groot',
|
||||
"emailAddress": 'iam@groot.com',
|
||||
|
@ -60,7 +61,7 @@ user = {
|
|||
"rawTypedData": json.dumps(message["data"])
|
||||
}
|
||||
|
||||
team = [user]
|
||||
test_team = [test_user]
|
||||
|
||||
milestones = [
|
||||
{
|
||||
|
@ -72,11 +73,21 @@ milestones = [
|
|||
}
|
||||
]
|
||||
|
||||
proposal = {
|
||||
"team": team,
|
||||
test_proposal = {
|
||||
"team": test_team,
|
||||
"crowdFundContractAddress": "0x20000",
|
||||
"content": "## My Proposal",
|
||||
"title": "Give Me Money",
|
||||
"milestones": milestones,
|
||||
"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
|
||||
|
||||
from ..config import BaseTestConfig
|
||||
from ..test_data import user, message
|
||||
from ..test_data import test_user, message
|
||||
|
||||
|
||||
class TestRequiredSignedMessageDecorator(BaseTestConfig):
|
||||
def test_required_sm_aborts_without_data_and_sig_headers(self):
|
||||
self.app.post(
|
||||
"/api/v1/users/",
|
||||
data=json.dumps(user),
|
||||
data=json.dumps(test_user),
|
||||
content_type='application/json'
|
||||
)
|
||||
|
||||
|
@ -53,7 +53,7 @@ class TestRequiredSignedMessageDecorator(BaseTestConfig):
|
|||
def test_required_sm_decorator_authorizes_when_recovered_address_matches_existing_user(self):
|
||||
self.app.post(
|
||||
"/api/v1/users/",
|
||||
data=json.dumps(user),
|
||||
data=json.dumps(test_user),
|
||||
content_type='application/json'
|
||||
)
|
||||
|
||||
|
@ -68,4 +68,4 @@ class TestRequiredSignedMessageDecorator(BaseTestConfig):
|
|||
response_json = response.json
|
||||
|
||||
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 json
|
||||
|
||||
from grant.proposal.models import Proposal
|
||||
from grant.user.models import User
|
||||
from ..config import BaseTestConfig
|
||||
from ..test_data import team, proposal
|
||||
from animal_case import animalify
|
||||
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(
|
||||
"/api/v1/proposals/",
|
||||
data=json.dumps(proposal_by_account),
|
||||
content_type='application/json'
|
||||
)
|
||||
class TestAPI(BaseUserConfig):
|
||||
# TODO create second signed message default user
|
||||
# @patch('grant.email.send.send_email')
|
||||
# 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
|
||||
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"])
|
||||
|
||||
def test_create_new_user_via_proposal_by_email(self):
|
||||
proposal_by_email = copy.deepcopy(proposal)
|
||||
del proposal_by_email["team"][0]["accountAddress"]
|
||||
|
||||
self.app.post(
|
||||
"/api/v1/proposals/",
|
||||
data=json.dumps(proposal_by_email),
|
||||
content_type='application/json'
|
||||
)
|
||||
|
||||
# User
|
||||
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.title, proposal_by_email["team"][0]["title"])
|
||||
# TODO create second signed message default user
|
||||
# def test_create_new_user_via_proposal_by_email(self):
|
||||
# self.remove_default_user()
|
||||
# proposal_by_email = copy.deepcopy(test_proposal)
|
||||
# del proposal_by_email["team"][0]["accountAddress"]
|
||||
#
|
||||
# resp = self.app.post(
|
||||
# "/api/v1/proposals/",
|
||||
# data=json.dumps(proposal_by_email),
|
||||
# headers=self.headers,
|
||||
# content_type='application/json'
|
||||
# )
|
||||
#
|
||||
# self.assertEqual(resp, 201)
|
||||
#
|
||||
# # User
|
||||
# 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.title, proposal_by_email["team"][0]["title"])
|
||||
|
||||
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"]
|
||||
|
||||
self.app.post(
|
||||
resp = self.app.post(
|
||||
"/api/v1/proposals/",
|
||||
data=json.dumps(proposal_by_email),
|
||||
headers=self.headers,
|
||||
content_type='application/json'
|
||||
)
|
||||
self.assertEqual(resp.status_code, 201)
|
||||
|
||||
# User
|
||||
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.title, proposal_by_email["team"][0]["title"])
|
||||
proposal_db = Proposal.query.filter_by(
|
||||
proposal_address=proposal["crowdFundContractAddress"]
|
||||
proposal_address=test_proposal["crowdFundContractAddress"]
|
||||
).first()
|
||||
self.assertEqual(proposal_db.team[0].id, user_db.id)
|
||||
|
||||
def test_associate_user_via_proposal_by_email_when_user_already_exists(self):
|
||||
proposal_by_email = copy.deepcopy(proposal)
|
||||
del proposal_by_email["team"][0]["accountAddress"]
|
||||
proposal_by_user_email = copy.deepcopy(test_proposal)
|
||||
del proposal_by_user_email["team"][0]["accountAddress"]
|
||||
|
||||
self.app.post(
|
||||
resp = self.app.post(
|
||||
"/api/v1/proposals/",
|
||||
data=json.dumps(proposal_by_email),
|
||||
data=json.dumps(proposal_by_user_email),
|
||||
headers=self.headers,
|
||||
content_type='application/json'
|
||||
)
|
||||
self.assertEqual(resp.status_code, 201)
|
||||
|
||||
# User
|
||||
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.title, proposal_by_email["team"][0]["title"])
|
||||
self.assertEqual(self.user.display_name, proposal_by_user_email["team"][0]["displayName"])
|
||||
self.assertEqual(self.user.title, proposal_by_user_email["team"][0]["title"])
|
||||
proposal_db = Proposal.query.filter_by(
|
||||
proposal_address=proposal["crowdFundContractAddress"]
|
||||
proposal_address=test_proposal["crowdFundContractAddress"]
|
||||
).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"
|
||||
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.title, new_proposal_by_email["team"][0]["title"])
|
||||
proposal_db = Proposal.query.filter_by(
|
||||
proposal_address=proposal["crowdFundContractAddress"]
|
||||
proposal_address=test_proposal["crowdFundContractAddress"]
|
||||
).first()
|
||||
self.assertEqual(proposal_db.team[0].id, user_db.id)
|
||||
|
||||
def test_get_all_users(self):
|
||||
self.app.post(
|
||||
"/api/v1/proposals/",
|
||||
data=json.dumps(proposal),
|
||||
data=json.dumps(test_proposal),
|
||||
content_type='application/json'
|
||||
)
|
||||
users_get_resp = self.app.get(
|
||||
|
@ -107,18 +124,17 @@ class TestAPI(BaseTestConfig):
|
|||
)
|
||||
|
||||
users_json = users_get_resp.json
|
||||
print(users_json)
|
||||
self.assertEqual(users_json[0]["displayName"], team[0]["displayName"])
|
||||
self.assertEqual(users_json[0]["displayName"], test_team[0]["displayName"])
|
||||
|
||||
def test_get_user_associated_with_proposal(self):
|
||||
self.app.post(
|
||||
"/api/v1/proposals/",
|
||||
data=json.dumps(proposal),
|
||||
data=json.dumps(test_proposal),
|
||||
content_type='application/json'
|
||||
)
|
||||
|
||||
data = {
|
||||
'proposalId': proposal["crowdFundContractAddress"]
|
||||
'proposalId': test_proposal["crowdFundContractAddress"]
|
||||
}
|
||||
|
||||
users_get_resp = self.app.get(
|
||||
|
@ -127,25 +143,25 @@ class TestAPI(BaseTestConfig):
|
|||
)
|
||||
|
||||
users_json = users_get_resp.json
|
||||
self.assertEqual(users_json[0]["avatar"]["imageUrl"], team[0]["avatar"]["link"])
|
||||
self.assertEqual(users_json[0]["socialMedias"][0]["socialMediaLink"], team[0]["socialMedias"][0]["link"])
|
||||
self.assertEqual(users_json[0]["displayName"], team[0]["displayName"])
|
||||
self.assertEqual(users_json[0]["avatar"]["imageUrl"], test_team[0]["avatar"]["link"])
|
||||
self.assertEqual(users_json[0]["socialMedias"][0]["socialMediaLink"], test_team[0]["socialMedias"][0]["link"])
|
||||
self.assertEqual(users_json[0]["displayName"], test_user["displayName"])
|
||||
|
||||
def test_get_single_user(self):
|
||||
self.app.post(
|
||||
"/api/v1/proposals/",
|
||||
data=json.dumps(proposal),
|
||||
data=json.dumps(test_proposal),
|
||||
content_type='application/json'
|
||||
)
|
||||
|
||||
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
|
||||
self.assertEqual(users_json["avatar"]["imageUrl"], team[0]["avatar"]["link"])
|
||||
self.assertEqual(users_json["socialMedias"][0]["socialMediaLink"], team[0]["socialMedias"][0]["link"])
|
||||
self.assertEqual(users_json["displayName"], team[0]["displayName"])
|
||||
self.assertEqual(users_json["avatar"]["imageUrl"], test_team[0]["avatar"]["link"])
|
||||
self.assertEqual(users_json["socialMedias"][0]["socialMediaLink"], test_team[0]["socialMedias"][0]["link"])
|
||||
self.assertEqual(users_json["displayName"], test_team[0]["displayName"])
|
||||
|
||||
@patch('grant.email.send.send_email')
|
||||
def test_create_user(self, mock_send_email):
|
||||
|
@ -153,15 +169,15 @@ class TestAPI(BaseTestConfig):
|
|||
|
||||
self.app.post(
|
||||
"/api/v1/users/",
|
||||
data=json.dumps(team[0]),
|
||||
data=json.dumps(test_team[0]),
|
||||
content_type='application/json'
|
||||
)
|
||||
|
||||
# User
|
||||
user_db = User.get_by_identifier(account_address=team[0]["accountAddress"])
|
||||
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"])
|
||||
user_db = User.get_by_identifier(account_address=test_team[0]["accountAddress"])
|
||||
self.assertEqual(user_db.display_name, test_team[0]["displayName"])
|
||||
self.assertEqual(user_db.title, test_team[0]["title"])
|
||||
self.assertEqual(user_db.account_address, test_team[0]["accountAddress"])
|
||||
|
||||
@patch('grant.email.send.send_email')
|
||||
def test_create_user_duplicate_400(self, mock_send_email):
|
||||
|
@ -170,64 +186,28 @@ class TestAPI(BaseTestConfig):
|
|||
|
||||
response = self.app.post(
|
||||
"/api/v1/users/",
|
||||
data=json.dumps(team[0]),
|
||||
data=json.dumps(test_team[0]),
|
||||
content_type='application/json'
|
||||
)
|
||||
|
||||
self.assertEqual(response.status_code, 409)
|
||||
|
||||
def test_update_user_remove_social_and_avatar(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'] = []
|
||||
updated_user['avatar'] = {}
|
||||
updated_user = animalify(copy.deepcopy(user_schema.dump(self.user)))
|
||||
updated_user["displayName"] = 'new display name'
|
||||
updated_user["avatar"] = None
|
||||
updated_user["socialMedias"] = None
|
||||
|
||||
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),
|
||||
headers=self.headers,
|
||||
content_type='application/json'
|
||||
)
|
||||
self.assert200(user_update_resp)
|
||||
|
||||
users_json = user_update_resp.json
|
||||
self.assertFalse(users_json["avatar"])
|
||||
self.assertFalse(len(users_json["socialMedias"]))
|
||||
self.assertEqual(users_json["displayName"], updated_user["displayName"])
|
||||
self.assertEqual(users_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"])
|
||||
user_json = user_update_resp.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"])
|
||||
|
|
|
@ -76,7 +76,7 @@ export class ProposalCampaignBlock extends React.Component<Props, State> {
|
|||
|
||||
sendTransaction = () => {
|
||||
const { proposal, fundCrowdFund } = this.props;
|
||||
fundCrowdFund(proposal.crowdFundContract, this.state.amountToRaise);
|
||||
fundCrowdFund(proposal, this.state.amountToRaise);
|
||||
|
||||
this.setState({ amountToRaise: '' });
|
||||
};
|
||||
|
|
|
@ -90,7 +90,7 @@ class CancelModal extends React.Component<Props> {
|
|||
};
|
||||
|
||||
private cancelProposal = () => {
|
||||
this.props.triggerRefund(this.props.proposal.crowdFundContract);
|
||||
this.props.triggerRefund(this.props.proposal);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -197,12 +197,12 @@ class GovernanceRefunds extends React.Component<Props> {
|
|||
}
|
||||
|
||||
voteRefund = (vote: boolean) => {
|
||||
this.props.voteRefund(this.props.proposal.crowdFundContract, vote);
|
||||
this.props.voteRefund(this.props.proposal, vote);
|
||||
};
|
||||
|
||||
withdrawRefund = () => {
|
||||
const { proposal, account } = this.props;
|
||||
this.props.withdrawRefund(proposal.crowdFundContract, account);
|
||||
this.props.withdrawRefund(proposal, account);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -234,18 +234,15 @@ export class Milestones extends React.Component<Props> {
|
|||
}
|
||||
|
||||
private requestPayout = (milestoneIndex: number) => {
|
||||
const { crowdFundContract } = this.props.proposal;
|
||||
this.props.requestMilestonePayout(crowdFundContract, milestoneIndex);
|
||||
this.props.requestMilestonePayout(this.props.proposal, milestoneIndex);
|
||||
};
|
||||
|
||||
private payPayout = (milestoneIndex: number) => {
|
||||
const { crowdFundContract } = this.props.proposal;
|
||||
this.props.payMilestonePayout(crowdFundContract, milestoneIndex);
|
||||
this.props.payMilestonePayout(this.props.proposal, milestoneIndex);
|
||||
};
|
||||
|
||||
private votePayout = (milestoneIndex: number, vote: boolean) => {
|
||||
const { crowdFundContract } = this.props.proposal;
|
||||
this.props.voteMilestonePayout(crowdFundContract, milestoneIndex, vote);
|
||||
this.props.voteMilestonePayout(this.props.proposal, milestoneIndex, vote);
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@ export class ProposalCard extends React.Component<Props> {
|
|||
}
|
||||
const {
|
||||
title,
|
||||
proposalId,
|
||||
proposalAddress,
|
||||
proposalUrlId,
|
||||
category,
|
||||
dateCreated,
|
||||
|
@ -77,7 +77,7 @@ export class ProposalCard extends React.Component<Props> {
|
|||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="ProposalCard-address">{proposalId}</div>
|
||||
<div className="ProposalCard-address">{proposalAddress}</div>
|
||||
|
||||
<div className="ProposalCard-info">
|
||||
<div
|
||||
|
|
|
@ -9,7 +9,7 @@ import { fetchProposal, fetchProposals } from 'modules/proposals/actions';
|
|||
import { PROPOSAL_CATEGORY } from 'api/constants';
|
||||
import { AppState } from 'store/reducers';
|
||||
import { Wei } from 'utils/units';
|
||||
import { TeamMember, AuthSignatureData } from 'types';
|
||||
import { TeamMember, AuthSignatureData, ProposalWithCrowdFund } from 'types';
|
||||
|
||||
type GetState = () => AppState;
|
||||
|
||||
|
@ -194,20 +194,21 @@ export function resetCreateCrowdFund() {
|
|||
}
|
||||
|
||||
export type TRequestMilestonePayout = typeof requestMilestonePayout;
|
||||
export function requestMilestonePayout(crowdFundContract: any, index: number) {
|
||||
export function requestMilestonePayout(proposal: ProposalWithCrowdFund, index: number) {
|
||||
return async (dispatch: Dispatch<any>, getState: GetState) => {
|
||||
dispatch({
|
||||
type: types.REQUEST_MILESTONE_PAYOUT_PENDING,
|
||||
});
|
||||
const state = getState();
|
||||
const account = state.web3.accounts[0];
|
||||
const { crowdFundContract, proposalId } = proposal;
|
||||
try {
|
||||
await crowdFundContract.methods
|
||||
.requestMilestonePayout(index)
|
||||
.send({ from: account })
|
||||
.once('confirmation', async () => {
|
||||
await sleep(5000);
|
||||
await dispatch(fetchProposal(crowdFundContract._address));
|
||||
await dispatch(fetchProposal(proposalId));
|
||||
dispatch({
|
||||
type: types.REQUEST_MILESTONE_PAYOUT_FULFILLED,
|
||||
});
|
||||
|
@ -223,20 +224,22 @@ export function requestMilestonePayout(crowdFundContract: any, index: number) {
|
|||
}
|
||||
|
||||
export type TPayMilestonePayout = typeof payMilestonePayout;
|
||||
export function payMilestonePayout(crowdFundContract: any, index: number) {
|
||||
export function payMilestonePayout(proposal: ProposalWithCrowdFund, index: number) {
|
||||
return async (dispatch: Dispatch<any>, getState: GetState) => {
|
||||
dispatch({
|
||||
type: types.PAY_MILESTONE_PAYOUT_PENDING,
|
||||
});
|
||||
const state = getState();
|
||||
const account = state.web3.accounts[0];
|
||||
const { crowdFundContract, proposalId } = proposal;
|
||||
|
||||
try {
|
||||
await crowdFundContract.methods
|
||||
.payMilestonePayout(index)
|
||||
.send({ from: account })
|
||||
.once('confirmation', async () => {
|
||||
await sleep(5000);
|
||||
await dispatch(fetchProposal(crowdFundContract._address));
|
||||
await dispatch(fetchProposal(proposalId));
|
||||
dispatch({
|
||||
type: types.PAY_MILESTONE_PAYOUT_FULFILLED,
|
||||
});
|
||||
|
@ -254,7 +257,7 @@ export function payMilestonePayout(crowdFundContract: any, index: number) {
|
|||
|
||||
// TODO: BigNumber me
|
||||
export type TSendTransaction = typeof fundCrowdFund;
|
||||
export function fundCrowdFund(crowdFundContract: any, value: number | string) {
|
||||
export function fundCrowdFund(proposal: ProposalWithCrowdFund, value: number | string) {
|
||||
return async (dispatch: Dispatch<any>, getState: GetState) => {
|
||||
dispatch({
|
||||
type: types.SEND_PENDING,
|
||||
|
@ -262,6 +265,7 @@ export function fundCrowdFund(crowdFundContract: any, value: number | string) {
|
|||
const state = getState();
|
||||
const web3 = state.web3.web3;
|
||||
const account = state.web3.accounts[0];
|
||||
const { crowdFundContract, proposalId } = proposal;
|
||||
|
||||
try {
|
||||
if (!web3) {
|
||||
|
@ -272,7 +276,7 @@ export function fundCrowdFund(crowdFundContract: any, value: number | string) {
|
|||
.send({ from: account, value: web3.utils.toWei(String(value), 'ether') })
|
||||
.once('confirmation', async () => {
|
||||
await sleep(5000);
|
||||
await dispatch(fetchProposal(crowdFundContract._address));
|
||||
await dispatch(fetchProposal(proposalId));
|
||||
dispatch({
|
||||
type: types.SEND_FULFILLED,
|
||||
});
|
||||
|
@ -289,7 +293,7 @@ export function fundCrowdFund(crowdFundContract: any, value: number | string) {
|
|||
}
|
||||
|
||||
export function voteMilestonePayout(
|
||||
crowdFundContract: any,
|
||||
proposal: ProposalWithCrowdFund,
|
||||
index: number,
|
||||
vote: boolean,
|
||||
) {
|
||||
|
@ -297,6 +301,7 @@ export function voteMilestonePayout(
|
|||
dispatch({ type: types.VOTE_AGAINST_MILESTONE_PAYOUT_PENDING });
|
||||
const state = getState();
|
||||
const account = state.web3.accounts[0];
|
||||
const { crowdFundContract, proposalId } = proposal;
|
||||
|
||||
try {
|
||||
await crowdFundContract.methods
|
||||
|
@ -304,7 +309,7 @@ export function voteMilestonePayout(
|
|||
.send({ from: account })
|
||||
.once('confirmation', async () => {
|
||||
await sleep(5000);
|
||||
await dispatch(fetchProposal(crowdFundContract._address));
|
||||
await dispatch(fetchProposal(proposalId));
|
||||
dispatch({ type: types.VOTE_AGAINST_MILESTONE_PAYOUT_FULFILLED });
|
||||
});
|
||||
} catch (err) {
|
||||
|
@ -318,11 +323,12 @@ export function voteMilestonePayout(
|
|||
};
|
||||
}
|
||||
|
||||
export function voteRefund(crowdFundContract: any, vote: boolean) {
|
||||
export function voteRefund(proposal: ProposalWithCrowdFund, vote: boolean) {
|
||||
return async (dispatch: Dispatch<any>, getState: GetState) => {
|
||||
dispatch({ type: types.VOTE_REFUND_PENDING });
|
||||
const state = getState();
|
||||
const account = state.web3.accounts[0];
|
||||
const { crowdFundContract, proposalId } = proposal;
|
||||
|
||||
try {
|
||||
await crowdFundContract.methods
|
||||
|
@ -330,7 +336,7 @@ export function voteRefund(crowdFundContract: any, vote: boolean) {
|
|||
.send({ from: account })
|
||||
.once('confirmation', async () => {
|
||||
await sleep(5000);
|
||||
await dispatch(fetchProposal(crowdFundContract._address));
|
||||
await dispatch(fetchProposal(proposalId));
|
||||
dispatch({ type: types.VOTE_REFUND_FULFILLED });
|
||||
});
|
||||
} catch (err) {
|
||||
|
@ -366,15 +372,16 @@ async function freezeContract(crowdFundContract: any, account: string) {
|
|||
}
|
||||
}
|
||||
|
||||
export function triggerRefund(crowdFundContract: any) {
|
||||
export function triggerRefund(proposal: ProposalWithCrowdFund) {
|
||||
return async (dispatch: Dispatch<any>, getState: GetState) => {
|
||||
dispatch({ type: types.WITHDRAW_REFUND_PENDING });
|
||||
const state = getState();
|
||||
const account = state.web3.accounts[0];
|
||||
const { crowdFundContract, proposalId } = proposal;
|
||||
|
||||
try {
|
||||
await freezeContract(crowdFundContract, account);
|
||||
await dispatch(fetchProposal(crowdFundContract._address));
|
||||
await dispatch(fetchProposal(proposalId));
|
||||
dispatch({ type: types.TRIGGER_REFUND_FULFILLED });
|
||||
} catch (err) {
|
||||
dispatch({
|
||||
|
@ -386,11 +393,12 @@ export function triggerRefund(crowdFundContract: any) {
|
|||
};
|
||||
}
|
||||
|
||||
export function withdrawRefund(crowdFundContract: any, address: string) {
|
||||
export function withdrawRefund(proposal: ProposalWithCrowdFund, address: string) {
|
||||
return async (dispatch: Dispatch<any>, getState: GetState) => {
|
||||
dispatch({ type: types.WITHDRAW_REFUND_PENDING });
|
||||
const state = getState();
|
||||
const account = state.web3.accounts[0];
|
||||
const { crowdFundContract, proposalId } = proposal;
|
||||
|
||||
try {
|
||||
await freezeContract(crowdFundContract, account);
|
||||
|
@ -399,7 +407,7 @@ export function withdrawRefund(crowdFundContract: any, address: string) {
|
|||
.send({ from: account })
|
||||
.once('confirmation', async () => {
|
||||
await sleep(5000);
|
||||
await dispatch(fetchProposal(crowdFundContract._address));
|
||||
await dispatch(fetchProposal(proposalId));
|
||||
dispatch({ type: types.WITHDRAW_REFUND_FULFILLED });
|
||||
});
|
||||
} catch (err) {
|
||||
|
|
Loading…
Reference in New Issue