zcash-grant-system/backend/grant/proposal/views.py

342 lines
11 KiB
Python
Raw Normal View History

from dateutil.parser import parse
2018-11-07 11:19:12 -08:00
from functools import wraps
import ast
2018-09-10 09:55:26 -07:00
2018-11-07 11:19:12 -08:00
from flask import Blueprint, g
2018-10-22 15:31:33 -07:00
from flask_yoloapi import endpoint, parameter
2018-09-10 09:55:26 -07:00
from sqlalchemy.exc import IntegrityError
from grant.comment.models import Comment, comment_schema, comments_schema
2018-09-10 09:55:26 -07:00
from grant.milestone.models import Milestone
from grant.user.models import User, SocialMedia, Avatar
2018-11-16 08:16:52 -08:00
from grant.email.send import send_email
2018-12-14 11:36:22 -08:00
from grant.utils.auth import requires_auth, requires_team_member_auth
2018-11-13 08:07:09 -08:00
from grant.utils.exceptions import ValidationException
2018-12-17 10:33:33 -08:00
from grant.utils.misc import is_email, make_url
from .models import(
Proposal,
proposals_schema,
proposal_schema,
ProposalUpdate,
proposal_update_schema,
ProposalContribution,
proposal_contribution_schema,
2018-11-26 15:47:24 -08:00
proposal_team,
2018-11-26 17:14:00 -08:00
ProposalTeamInvite,
proposal_team_invite_schema,
db
)
2018-11-25 20:02:35 -08:00
import traceback
2018-09-10 09:55:26 -07:00
2018-09-18 15:20:17 -07:00
blueprint = Blueprint("proposal", __name__, url_prefix="/api/v1/proposals")
2018-09-10 09:55:26 -07:00
@blueprint.route("/<proposal_id>", methods=["GET"])
@endpoint.api()
2018-09-10 09:55:26 -07:00
def get_proposal(proposal_id):
proposal = Proposal.query.filter_by(id=proposal_id).first()
2018-09-10 09:55:26 -07:00
if proposal:
return proposal_schema.dump(proposal)
2018-09-10 09:55:26 -07:00
else:
return {"message": "No proposal matching id"}, 404
2018-09-10 09:55:26 -07:00
@blueprint.route("/<proposal_id>/comments", methods=["GET"])
@endpoint.api()
2018-09-10 09:55:26 -07:00
def get_proposal_comments(proposal_id):
proposal = Proposal.query.filter_by(id=proposal_id).first()
if not proposal:
return {"message": "No proposal matching id"}, 404
2018-12-14 11:36:22 -08:00
# Only pull top comments, replies will be attached to them
comments = Comment.query.filter_by(proposal_id=proposal_id, parent_comment_id=None)
num_comments = Comment.query.filter_by(proposal_id=proposal_id).count()
return {
"proposalId": proposal_id,
"totalComments": num_comments,
"comments": comments_schema.dump(comments)
}
2018-09-10 09:55:26 -07:00
@blueprint.route("/<proposal_id>/comments", methods=["POST"])
2018-12-14 11:36:22 -08:00
@requires_auth
2018-10-22 15:31:33 -07:00
@endpoint.api(
parameter('comment', type=str, required=True),
2018-12-14 11:36:22 -08:00
parameter('parentCommentId', type=int, required=False)
2018-10-22 15:31:33 -07:00
)
2018-12-17 10:33:33 -08:00
def post_proposal_comments(proposal_id, comment, parent_comment_id):
# Make sure proposal exists
proposal = Proposal.query.filter_by(id=proposal_id).first()
if not proposal:
2018-10-22 15:31:33 -07:00
return {"message": "No proposal matching id"}, 404
# Make sure the parent comment exists
if parent_comment_id:
parent = Comment.query.filter_by(id=parent_comment_id).first()
if not parent:
return {"message": "Parent comment doesnt exist"}, 400
# Make the comment
comment = Comment(
proposal_id=proposal_id,
user_id=g.current_user.id,
parent_comment_id=parent_comment_id,
content=comment
)
db.session.add(comment)
db.session.commit()
dumped_comment = comment_schema.dump(comment)
return dumped_comment, 201
2018-09-10 09:55:26 -07:00
@blueprint.route("/", methods=["GET"])
2018-10-22 15:31:33 -07:00
@endpoint.api(
parameter('stage', type=str, required=False)
)
def get_proposals(stage):
2018-09-10 09:55:26 -07:00
if stage:
proposals = (
2018-11-13 08:07:09 -08:00
Proposal.query.filter_by(status="LIVE", stage=stage)
2018-12-14 11:36:22 -08:00
.order_by(Proposal.date_created.desc())
.all()
2018-09-10 09:55:26 -07:00
)
else:
proposals = (
Proposal.query.filter_by(status="LIVE")
.order_by(Proposal.date_created.desc())
.all()
)
dumped_proposals = proposals_schema.dump(proposals)
2018-12-14 11:36:22 -08:00
return dumped_proposals
# except Exception as e:
# print(e)
# print(traceback.format_exc())
# return {"message": "Oops! Something went wrong."}, 500
2018-09-10 09:55:26 -07:00
2018-11-13 08:07:09 -08:00
@blueprint.route("/drafts", methods=["POST"])
2018-12-14 11:36:22 -08:00
@requires_auth
2018-11-13 08:07:09 -08:00
@endpoint.api()
def make_proposal_draft():
proposal = Proposal.create(status="DRAFT")
proposal.team.append(g.current_user)
db.session.add(proposal)
db.session.commit()
return proposal_schema.dump(proposal), 201
2018-09-10 09:55:26 -07:00
2018-11-26 15:47:24 -08:00
2018-11-13 08:07:09 -08:00
@blueprint.route("/drafts", methods=["GET"])
2018-12-14 11:36:22 -08:00
@requires_auth
2018-11-13 08:07:09 -08:00
@endpoint.api()
def get_proposal_drafts():
proposals = (
Proposal.query
.filter_by(status="DRAFT")
.join(proposal_team)
.filter(proposal_team.c.user_id == g.current_user.id)
.order_by(Proposal.date_created.desc())
.all()
2018-09-10 09:55:26 -07:00
)
2018-11-13 08:07:09 -08:00
return proposals_schema.dump(proposals), 200
2018-09-10 09:55:26 -07:00
2018-12-14 11:36:22 -08:00
2018-11-13 08:07:09 -08:00
@blueprint.route("/<proposal_id>", methods=["PUT"])
@requires_team_member_auth
2018-10-22 15:31:33 -07:00
@endpoint.api(
2018-11-13 08:07:09 -08:00
parameter('title', type=str),
parameter('brief', type=str),
parameter('category', type=str),
2018-11-14 09:27:40 -08:00
parameter('content', type=str),
2018-11-13 08:07:09 -08:00
parameter('target', type=str),
parameter('payoutAddress', type=str),
parameter('deadlineDuration', type=int),
parameter('milestones', type=list)
2018-10-22 15:31:33 -07:00
)
def update_proposal(milestones, proposal_id, **kwargs):
2018-11-13 08:07:09 -08:00
# Update the base proposal fields
try:
g.current_proposal.update(**kwargs)
2018-11-13 08:07:09 -08:00
except ValidationException as e:
return {"message": "Invalid proposal parameters: {}".format(str(e))}, 400
db.session.add(g.current_proposal)
2018-09-26 01:46:30 -07:00
2018-11-13 08:07:09 -08:00
# Delete & re-add milestones
[db.session.delete(x) for x in g.current_proposal.milestones]
2018-11-13 08:07:09 -08:00
if milestones:
for mdata in milestones:
m = Milestone(
title=mdata["title"],
content=mdata["content"],
date_estimated=parse(mdata["dateEstimated"]),
2018-11-13 08:07:09 -08:00
payout_percent=str(mdata["payoutPercent"]),
immediate_payout=mdata["immediatePayout"],
proposal_id=g.current_proposal.id
)
2018-11-13 08:07:09 -08:00
db.session.add(m)
2018-12-14 11:36:22 -08:00
2018-11-13 08:07:09 -08:00
# Commit
db.session.commit()
return proposal_schema.dump(g.current_proposal), 200
2018-09-10 09:55:26 -07:00
2018-09-26 01:46:30 -07:00
@blueprint.route("/<proposal_id>", methods=["DELETE"])
2018-11-13 08:07:09 -08:00
@requires_team_member_auth
@endpoint.api()
2018-11-15 08:02:16 -08:00
def delete_proposal_draft(proposal_id):
2018-11-13 08:07:09 -08:00
if g.current_proposal.status != 'DRAFT':
return {"message": "Cannot delete non-draft proposals"}, 400
db.session.delete(g.current_proposal)
db.session.commit()
return None, 202
2018-09-10 09:55:26 -07:00
2018-09-26 01:46:30 -07:00
2018-11-13 08:07:09 -08:00
@blueprint.route("/<proposal_id>/publish", methods=["PUT"])
@requires_team_member_auth
@endpoint.api()
def publish_proposal(proposal_id):
2018-09-10 09:55:26 -07:00
try:
2018-11-13 08:07:09 -08:00
g.current_proposal.publish()
except ValidationException as e:
return {"message": "Invalid proposal parameters: {}".format(str(e))}, 400
db.session.add(g.current_proposal)
db.session.commit()
return proposal_schema.dump(g.current_proposal), 200
@blueprint.route("/<proposal_id>/updates", methods=["GET"])
@endpoint.api()
def get_proposal_updates(proposal_id):
proposal = Proposal.query.filter_by(id=proposal_id).first()
if proposal:
dumped_proposal = proposal_schema.dump(proposal)
return dumped_proposal["updates"]
else:
return {"message": "No proposal matching id"}, 404
@blueprint.route("/<proposal_id>/updates/<update_id>", methods=["GET"])
@endpoint.api()
def get_proposal_update(proposal_id, update_id):
proposal = Proposal.query.filter_by(id=proposal_id).first()
if proposal:
update = ProposalUpdate.query.filter_by(proposal_id=proposal.id, id=update_id).first()
if update:
return proposal_update_schema.dump(update)
else:
return {"message": "No update matching id"}
else:
return {"message": "No proposal matching id"}, 404
@blueprint.route("/<proposal_id>/updates", methods=["POST"])
2018-11-07 11:19:12 -08:00
@requires_team_member_auth
@endpoint.api(
parameter('title', type=str, required=True),
parameter('content', type=str, required=True)
)
def post_proposal_update(proposal_id, title, content):
2018-11-07 11:19:12 -08:00
update = ProposalUpdate(
proposal_id=g.current_proposal.id,
title=title,
content=content
)
db.session.add(update)
db.session.commit()
2018-11-07 11:19:12 -08:00
dumped_update = proposal_update_schema.dump(update)
return dumped_update, 201
2018-12-14 11:36:22 -08:00
2018-11-16 08:16:52 -08:00
@blueprint.route("/<proposal_id>/invite", methods=["POST"])
@requires_team_member_auth
@endpoint.api(
parameter('address', type=str, required=True)
)
def post_proposal_team_invite(proposal_id, address):
invite = ProposalTeamInvite(
proposal_id=proposal_id,
address=address
)
db.session.add(invite)
db.session.commit()
# Send email
# TODO: Move this to some background task / after request action
email = address
2018-12-14 11:36:22 -08:00
user = User.get_by_email(email_address=address)
2018-11-16 08:16:52 -08:00
if user:
email = user.email_address
if is_email(email):
send_email(email, 'team_invite', {
'user': user,
'inviter': g.current_user,
2018-12-03 18:45:18 -08:00
'proposal': g.current_proposal,
2018-12-17 10:33:33 -08:00
'invite_url': make_url(f'/profile/{user.id}' if user else '/auth')
2018-11-16 08:16:52 -08:00
})
return proposal_team_invite_schema.dump(invite), 201
2018-11-26 17:14:00 -08:00
2018-11-16 08:16:52 -08:00
@blueprint.route("/<proposal_id>/invite/<id_or_address>", methods=["DELETE"])
@requires_team_member_auth
@endpoint.api()
def delete_proposal_team_invite(proposal_id, id_or_address):
invite = ProposalTeamInvite.query.filter(
(ProposalTeamInvite.id == id_or_address) |
(ProposalTeamInvite.address == id_or_address)
).first()
if not invite:
return {"message": "No invite found given {}".format(id_or_address)}, 404
if invite.accepted:
return {"message": "Cannot delete an invite that has been accepted"}, 403
db.session.delete(invite)
db.session.commit()
2018-11-26 17:14:00 -08:00
return None, 202
@blueprint.route("/<proposal_id>/contributions", methods=["GET"])
@endpoint.api()
def get_proposal_contributions(proposal_id):
proposal = Proposal.query.filter_by(id=proposal_id).first()
if proposal:
dumped_proposal = proposal_schema.dump(proposal)
return dumped_proposal["contributions"]
else:
return {"message": "No proposal matching id"}, 404
@blueprint.route("/<proposal_id>/contributions/<contribution_id>", methods=["GET"])
@endpoint.api()
def get_proposal_contribution(proposal_id, contribution_id):
proposal = Proposal.query.filter_by(id=proposal_id).first()
if proposal:
contribution = ProposalContribution.query.filter_by(tx_id=contribution_id).first()
if contribution:
return proposal_contribution_schema.dump(contribution)
else:
return {"message": "No contribution matching id"}
else:
return {"message": "No proposal matching id"}, 404
@blueprint.route("/<proposal_id>/contributions", methods=["POST"])
2018-12-14 11:36:22 -08:00
@requires_auth
@endpoint.api(
parameter('amount', type=str, required=True)
)
def post_proposal_contribution(proposal_id, amount):
proposal = Proposal.query.filter_by(id=proposal_id).first()
if not proposal:
return {"message": "No proposal matching id"}, 404
contribution = ProposalContribution(
proposal_id=proposal_id,
user_id=g.current_user.id,
amount=amount
)
db.session.add(contribution)
db.session.commit()
dumped_contribution = proposal_contribution_schema.dump(contribution)
return dumped_contribution, 201