Ejemplo n.º 1
0
    def approve_pending(self, is_approve, reject_reason=None):
        self.validate_publishable()
        # specific validation
        if not self.status == ProposalStatus.PENDING:
            raise ValidationException(f"Proposal must be pending to approve or reject")

        if is_approve:
            self.status = ProposalStatus.APPROVED
            self.date_approved = datetime.datetime.now()
            for t in self.team:
                send_email(t.email_address, 'proposal_approved', {
                    'user': t,
                    'proposal': self,
                    'proposal_url': make_url(f'/proposals/{self.id}'),
                    'admin_note': 'Congratulations! Your proposal has been approved.'
                })
        else:
            if not reject_reason:
                raise ValidationException("Please provide a reason for rejecting the proposal")
            self.status = ProposalStatus.REJECTED
            self.reject_reason = reject_reason
            for t in self.team:
                send_email(t.email_address, 'proposal_rejected', {
                    'user': t,
                    'proposal': self,
                    'proposal_url': make_url(f'/proposals/{self.id}'),
                    'admin_note': reject_reason
                })
Ejemplo n.º 2
0
def reject_permanently_proposal(proposal_id, reject_reason):
    proposal = Proposal.query.get(proposal_id)

    if not proposal:
        return {"message": "No proposal found."}, 404

    reject_permanently_statuses = [
        ProposalStatus.REJECTED, ProposalStatus.PENDING
    ]

    if proposal.status not in reject_permanently_statuses:
        return {"message": "Proposal status is not REJECTED or PENDING."}, 401

    proposal.status = ProposalStatus.REJECTED_PERMANENTLY
    proposal.reject_reason = reject_reason

    db.session.add(proposal)
    db.session.commit()

    for user in proposal.team:
        send_email(
            user.email_address, 'proposal_rejected_permanently', {
                'user': user,
                'proposal': proposal,
                'proposal_url': make_url(f'/proposals/{proposal.id}'),
                'admin_note': reject_reason,
                'profile_rejected_url': make_url('/profile?tab=rejected'),
            })

    return proposal_schema.dump(proposal)
Ejemplo n.º 3
0
 def set_password(self, password: str):
     self.password = hash_password(password)
     db.session.commit()
     send_email(
         self.email_address, 'change_password', {
             'display_name': self.display_name,
             'recover_url': make_url('/auth/recover'),
             'contact_url': make_url('/contact')
         })
Ejemplo n.º 4
0
def make_markdown(event, field):
    text = getattr(event, field)
    if event.user != None:
        text = text.replace(
            "$user",
            "[{}]({})".format(event.user.display_name,
                              make_url("/profile/{}".format(event.user.id))))
    if event.proposal != None:
        text = text.replace(
            "$proposal", "[{}]({})".format(
                event.proposal.title,
                make_url("/proposals/{}".format(event.proposal.id))))
    return text
Ejemplo n.º 5
0
def paid_milestone_payout_request(id, mid):
    proposal = Proposal.query.filter_by(id=id).first()
    if not proposal:
        return {"message": "No proposal matching id"}, 404
    if not proposal.status == ProposalStatus.LIVE:
        return {"message": "Proposal is not live"}, 400
    for ms in proposal.milestones:
        if ms.id == int(mid):
            ms.mark_paid()
            admin.admin_log("MILESTONE_PAID", f"Paid milestone #{ms.index + 1} ({ms.title}) for proposal {proposal.id} ({proposal.title})")
            db.session.add(ms)
            db.session.flush()
            # check if this is the final ms, and update proposal.stage
            num_paid = reduce(lambda a, x: a + (1 if x.stage == MilestoneStage.PAID else 0), proposal.milestones, 0)
            if num_paid == len(proposal.milestones):
                proposal.stage = ProposalStage.COMPLETED  # WIP -> COMPLETED
                db.session.add(proposal)
                db.session.flush()
            db.session.commit()
            # email TEAM that payout request was PAID
            for member in proposal.team:
                send_email(member.email_address, 'milestone_paid', {
                    'proposal': proposal,
                    'milestone': ms,
                    'amount': ms.payout_amount,
                    'proposal_milestones_url': make_url(f'/proposals/{proposal.id}?tab=milestones'),
                })
            # email FOLLOWERS that milestone was accepted
            proposal.send_follower_email('followed_proposal_milestone', email_args={'milestone':ms}, url_suffix='?tab=milestones')
            return proposal_schema.dump(proposal), 200

    return {"message": "No milestone matching id"}, 404
Ejemplo n.º 6
0
    def process_task(task):
        from grant.proposal.models import Proposal
        proposal = Proposal.query.filter_by(
            id=task.blob["proposal_id"]).first()

        # If it was deleted, canceled, or successful, just noop out
        if not proposal or proposal.is_funded or proposal.stage != ProposalStage.FUNDING_REQUIRED:
            return

        # Otherwise, mark it as failed and inform everyone
        proposal.stage = ProposalStage.FAILED
        db.session.add(proposal)
        db.session.commit()

        # Send emails to team & contributors
        for u in proposal.team:
            send_email(u.email_address, 'proposal_failed', {
                'proposal': proposal,
            })
        for u in proposal.contributors:
            send_email(
                u.email_address, 'contribution_proposal_failed', {
                    'proposal':
                    proposal,
                    'refund_address':
                    u.settings.refund_address,
                    'account_settings_url':
                    make_url('/profile/settings?tab=account')
                })
Ejemplo n.º 7
0
def reject_permanently_ccr(ccr_id, reject_reason):
    ccr = CCR.query.get(ccr_id)

    if not ccr:
        return {"message": "No CCR found."}, 404

    reject_permanently_statuses = [CCRStatus.REJECTED, CCRStatus.PENDING]

    if ccr.status not in reject_permanently_statuses:
        return {"message": "CCR status is not REJECTED or PENDING."}, 401

    ccr.status = CCRStatus.REJECTED_PERMANENTLY
    ccr.reject_reason = reject_reason

    db.session.add(ccr)
    db.session.commit()

    send_email(
        ccr.author.email_address, 'ccr_rejected_permanently', {
            'user': ccr.author,
            'ccr': ccr,
            'admin_note': reject_reason,
            'profile_rejected_url': make_url('/profile?tab=rejected')
        })

    return ccr_schema.dump(ccr)
Ejemplo n.º 8
0
def accept_milestone_payout_request(proposal_id, milestone_id):
    if not g.current_proposal.is_funded:
        return {"message": "Proposal is not fully funded"}, 400
    for ms in g.current_proposal.milestones:
        if ms.id == int(milestone_id):
            ms.accept_request(g.current_user.id)
            db.session.add(ms)
            db.session.commit()
            # email TEAM that payout request accepted
            amount = Decimal(ms.payout_percent) * Decimal(
                g.current_proposal.target) / 100
            for member in g.current_proposal.team:
                send_email(
                    member.email_address, 'milestone_accept', {
                        'proposal':
                        g.current_proposal,
                        'amount':
                        amount,
                        'proposal_milestones_url':
                        make_url(
                            f'/proposals/{g.current_proposal.id}?tab=milestones'
                        ),
                    })
            return proposal_schema.dump(g.current_proposal), 200

    return {"message": "No milestone matching id"}, 404
Ejemplo n.º 9
0
    def process_task(task):
        from grant.proposal.models import Proposal, ProposalUpdate
        from grant.milestone.models import Milestone

        proposal_id = task.blob["proposal_id"]
        milestone_id = task.blob["milestone_id"]
        update_count = task.blob["update_count"]

        proposal = Proposal.query.filter_by(id=proposal_id).first()
        milestone = Milestone.query.filter_by(id=milestone_id).first()
        current_update_count = len(
            ProposalUpdate.query.filter_by(proposal_id=proposal_id).all())

        # if proposal was deleted or cancelled, noop out
        if not proposal or proposal.status == ProposalStatus.DELETED or proposal.stage == ProposalStage.CANCELED:
            return

        # if milestone was deleted, noop out
        if not milestone:
            return

        # if milestone payout has been requested or an update has been posted, noop out
        if current_update_count > update_count or milestone.date_requested:
            return

        # send email to arbiter notifying milestone deadline has been missed
        send_email(
            proposal.arbiter.user.email_address, 'milestone_deadline', {
                'proposal':
                proposal,
                'proposal_milestones_url':
                make_url(f'/proposals/{proposal.id}?tab=milestones'),
            })
Ejemplo n.º 10
0
def post_proposal_update(proposal_id, title, content):
    update = ProposalUpdate(proposal_id=g.current_proposal.id,
                            title=title,
                            content=content)
    db.session.add(update)
    db.session.commit()

    # Send email to all contributors
    for u in g.current_proposal.contributors:
        send_email(
            u.email_address, 'contribution_update', {
                'proposal':
                g.current_proposal,
                'proposal_update':
                update,
                'update_url':
                make_url(
                    f'/proposals/{proposal_id}?tab=updates&update={update.id}'
                ),
            })

    # Send email to all followers
    g.current_proposal.send_follower_email("followed_proposal_update",
                                           url_suffix="?tab=updates")

    dumped_update = proposal_update_schema.dump(update)
    return dumped_update, 201
Ejemplo n.º 11
0
 def send_verification_email(self):
     send_email(
         self.email_address, 'signup', {
             'display_name':
             self.display_name,
             'confirm_url':
             make_url(f'/email/verify?code={self.email_verification.code}')
         })
Ejemplo n.º 12
0
 def send_follower_email(self, type: str, email_args={}, url_suffix=''):
     for u in self.followers:
         send_email(u.email_address, type, {
             'user': u,
             'proposal': self,
             'proposal_url': make_url(f'/proposals/{self.id}{url_suffix}'),
             **email_args
         })
Ejemplo n.º 13
0
def set_arbiter(proposal_id, user_id):
    proposal = Proposal.query.filter(Proposal.id == proposal_id).first()
    if not proposal:
        return {"message": "Proposal not found"}, 404

    for member in proposal.team:
        if member.id == user_id:
            return {
                "message": "Cannot set proposal team member as arbiter"
            }, 400

    if proposal.is_failed:
        return {"message": "Cannot set arbiter on failed proposal"}, 400

    if proposal.version == '2' and not proposal.accepted_with_funding:
        return {
            "message":
            "Cannot set arbiter, proposal has not been accepted with funding"
        }, 400

    user = User.query.filter(User.id == user_id).first()
    if not user:
        return {"message": "User not found"}, 404

    # send email
    code = user.email_verification.code
    send_email(
        user.email_address, 'proposal_arbiter', {
            'proposal':
            proposal,
            'proposal_url':
            make_url(f'/proposals/{proposal.id}'),
            'accept_url':
            make_url(f'/email/arbiter?code={code}&proposalId={proposal.id}'),
        })
    proposal.arbiter.user = user
    proposal.arbiter.status = ProposalArbiterStatus.NOMINATED
    db.session.add(proposal.arbiter)
    db.session.commit()

    return {
        'proposal': proposal_schema.dump(proposal),
        'user': admin_user_schema.dump(user)
    }, 200
Ejemplo n.º 14
0
def paid_milestone_payout_request(id, mid, tx_id):
    proposal = Proposal.query.filter_by(id=id).first()
    if not proposal:
        return {"message": "No proposal matching id"}, 404
    if not proposal.is_funded:
        return {"message": "Proposal is not fully funded"}, 400
    for ms in proposal.milestones:
        if ms.id == int(mid):
            is_final_milestone = False
            ms.mark_paid(tx_id)
            db.session.add(ms)
            db.session.flush()
            # check if this is the final ms, and update proposal.stage
            num_paid = reduce(
                lambda a, x: a + (1 if x.stage == MilestoneStage.PAID else 0),
                proposal.milestones, 0)
            if num_paid == len(proposal.milestones):
                is_final_milestone = True
                proposal.stage = ProposalStage.COMPLETED  # WIP -> COMPLETED
                db.session.add(proposal)
                db.session.flush()
            db.session.commit()
            # email TEAM that payout request was PAID
            amount = Decimal(ms.payout_percent) * Decimal(
                proposal.target) / 100
            for member in proposal.team:
                send_email(
                    member.email_address, 'milestone_paid', {
                        'proposal':
                        proposal,
                        'milestone':
                        ms,
                        'amount':
                        amount,
                        'tx_explorer_url':
                        make_explore_url(tx_id),
                        'proposal_milestones_url':
                        make_url(f'/proposals/{proposal.id}?tab=milestones'),
                    })

            # email FOLLOWERS that milestone was accepted
            proposal.send_follower_email(
                "followed_proposal_milestone",
                email_args={"milestone": ms},
                url_suffix="?tab=milestones",
            )

            if not is_final_milestone:
                Milestone.set_v2_date_estimates(proposal)
                db.session.commit()

            return proposal_schema.dump(proposal), 200

    return {"message": "No milestone matching id"}, 404
Ejemplo n.º 15
0
 def send_recovery_email(self):
     existing = self.email_recovery
     if existing:
         db.session.delete(existing)
     er = EmailRecovery(user_id=self.id)
     db.session.add(er)
     db.session.commit()
     send_email(
         self.email_address, 'recover', {
             'display_name': self.display_name,
             'recover_url': make_url(f'/email/recover?code={er.code}'),
         })
Ejemplo n.º 16
0
 def set_email(self, email: str):
     # Update email address
     old_email = self.email_address
     self.email_address = email
     # Delete old verification(s?)
     old_evs = EmailVerification.query.filter_by(user_id=self.id).all()
     for old_ev in old_evs:
         db.session.delete(old_ev)
     # Generate a new one
     ev = EmailVerification(user_id=self.id)
     db.session.add(ev)
     # Save changes & send notification & verification emails
     db.session.commit()
     send_email(old_email, 'change_email_old', {
         'display_name': self.display_name,
         'contact_url': make_url('/contact')
     })
     send_email(
         self.email_address, 'change_email', {
             'display_name': self.display_name,
             'confirm_url': make_url(f'/email/verify?code={ev.code}')
         })
Ejemplo n.º 17
0
    def cancel(self):
        if self.status != ProposalStatus.LIVE:
            raise ValidationException("Cannot cancel a proposal until it's live")

        self.stage = ProposalStage.CANCELED
        db.session.add(self)
        db.session.flush()

        # Send emails to team & contributors
        for u in self.team:
            send_email(u.email_address, 'proposal_canceled', {
                'proposal': self,
                'support_url': make_url('/contact'),
            })
Ejemplo n.º 18
0
    def process_task(task):
        from grant.proposal.models import ProposalContribution
        contribution = ProposalContribution.query.filter_by(
            id=task.blob["contribution_id"]).first()

        # If it's missing or not pending, noop out
        if not contribution or contribution.status != ContributionStatus.PENDING:
            return

        # Otherwise, inform the user (if not anonymous)
        if contribution.user:
            send_email(
                contribution.user.email_address, 'contribution_expired', {
                    'contribution':
                    contribution,
                    'proposal':
                    contribution.proposal,
                    'contact_url':
                    make_url('/contact'),
                    'profile_url':
                    make_url(f'/profile/{contribution.user.id}'),
                    'proposal_url':
                    make_url(f'/proposals/{contribution.proposal.id}'),
                })
Ejemplo n.º 19
0
def reject_milestone_payout_request(proposal_id, milestone_id, reason):
    proposal = Proposal.query.filter_by(id=proposal_id).first()
    if not proposal:
        return {"message": "No proposal matching id"}, 404
    for ms in proposal.milestones:
        if ms.id == int(milestone_id):
            ms.reject_request(reason)
            admin.admin_log("MILESTONE_REJECT", f"Rejected milestone #{ms.index + 1} ({ms.title}) for proposal {proposal.id} ({proposal.title}) with reason '{reason}'")
            db.session.add(ms)
            db.session.commit()
            # email TEAM that payout request was rejected
            for member in proposal.team:
                send_email(member.email_address, 'milestone_reject', {
                    'proposal': proposal,
                    'admin_note': reason,
                    'proposal_milestones_url': make_url(f'/proposals/{proposal.id}?tab=milestones'),
                })
            return proposal_schema.dump(proposal), 200

    return {"message": "No milestone matching id"}, 404
Ejemplo n.º 20
0
def generate_email(type, email_args, user=None):
    info = get_info_lookup[type](email_args)
    body_text = render_template(
        'emails/%s.txt' % (type),
        args=email_args,
        UI=UI,
    )
    body_html = render_template(
        'emails/%s.html' % (type),
        args=email_args,
        UI=UI,
    )

    template_args = {**default_template_args}
    if user:
        template_args['unsubscribe_url'] = make_url('/email/unsubscribe?code={}'.format(user.email_verification.code))

    html = render_template(
        'emails/template.html',
        args={
            **template_args,
            **info,
            'body': Markup(body_html),
        },
        UI=UI,
    )
    text = render_template(
        'emails/template.txt',
        args={
            **template_args,
            **info,
            'body': body_text,
        },
        UI=UI,
    )

    return {
        'info': info,
        'html': html,
        'text': text
    }
Ejemplo n.º 21
0
def update_rfw_worker_accept(rfw_id, worker_id, is_accept, message=''):
    rfw = rfw_models.RFW.query.get(rfw_id)
    if not rfw:
        return {"message": "No RFW matching that id"}, 404
    w = rfw_models.RFWWorker.query.get(worker_id)
    if not w:
        return {"message": "No worker matching that id"}, 404
    if is_accept:
        rfw.accept_worker_by_id(worker_id, message)
        admin.admin_log("WORKER_ACCEPT", f"Accepted worker {worker_id} for RFW {rfw.id} with message '{message}'")
    else:
        rfw.reject_worker_by_id(worker_id, message)
        admin.admin_log("WORKER_REJECT", f"Rejected worker {worker_id} for RFW {rfw.id} with message '{message}'")
    # notify worker
    send_email(w.user.email_address, 'worker_approved' if is_accept else 'worker_rejected', {
        'rfw': rfw,
        'message': message,
        'rfw_url': make_url(f'/rfws/{rfw.id}'),
    })
    db.session.commit()
    return rfw_models.rfw_schemas.single_admin.dump(rfw)
Ejemplo n.º 22
0
def update_rfw_milestone_claim_accept(rfw_id, ms_id, claim_id, is_accept, message=''):
    rfw = rfw_models.RFW.query.get(rfw_id)
    if not rfw:
        return {"message": "No RFW matching that id"}, 404
    if is_accept:
        rfw.accept_milestone_claim(ms_id, claim_id, message)
        admin.admin_log("CLAIM_ACCEPT", f"Accepted claim {claim_id} for RFW {rfw_id} with message '{message}'")
    else:
        rfw.reject_milestone_claim(ms_id, claim_id, message)
        admin.admin_log("CLAIM_REJECT", f"Rejected claim {claim_id} for RFW {rfw_id} with message '{message}'")
    # notify worker
    ms = rfw.get_milestone_by_id(ms_id)
    w = ms.get_claim_by_id(claim_id).worker
    send_email(w.user.email_address, 'work_milestone_accepted' if is_accept else 'work_milestone_rejected', {
        'rfw': rfw,
        'milestone': ms,
        'message': message,
        'rfw_url': make_url(f'/rfws/{rfw.id}'),
    })
    db.session.commit()
    return rfw_models.rfw_schemas.single_admin.dump(rfw)
Ejemplo n.º 23
0
def request_milestone_payout(proposal_id, milestone_id):
    if not g.current_proposal.is_funded:
        return {"message": "Proposal is not fully funded"}, 400
    for ms in g.current_proposal.milestones:
        if ms.id == int(milestone_id):
            ms.request_payout(g.current_user.id)
            db.session.add(ms)
            db.session.commit()
            # email ARBITER to review payout request
            send_email(
                g.current_proposal.arbiter.user.email_address,
                'milestone_request', {
                    'proposal':
                    g.current_proposal,
                    'proposal_milestones_url':
                    make_url(
                        f'/proposals/{g.current_proposal.id}?tab=milestones'),
                })
            return proposal_schema.dump(g.current_proposal), 200

    return {"message": "No milestone matching id"}, 404
Ejemplo n.º 24
0
    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,
            display_name=display_name,
            title=title
        )
        db.session.add(user)
        db.session.flush()

        # Setup & send email verification
        ev = EmailVerification(user_id=user.id)
        db.session.add(ev)
        db.session.commit()

        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
Ejemplo n.º 25
0
def set_user_arbiter(user_id, proposal_id, is_accept):
    try:
        proposal = Proposal.query.filter_by(id=int(proposal_id)).first()
        if not proposal:
            return {"message": "No such proposal"}, 404

        if is_accept:
            proposal.arbiter.accept_nomination(g.current_user.id)

            for user in proposal.team:
                send_email(
                    user.email_address, 'proposal_arbiter_assigned', {
                        'user': user,
                        'proposal': proposal,
                        'proposal_url': make_url(f'/proposals/{proposal.id}')
                    })

            return {"message": "Accepted nomination"}, 200
        else:
            proposal.arbiter.reject_nomination(g.current_user.id)
            return {"message": "Rejected nomination"}, 200

    except ValidationException as e:
        return {"message": str(e)}, 400
Ejemplo n.º 26
0
def reject_milestone_payout_request(proposal_id, milestone_id, reason):
    if not g.current_proposal.is_funded:
        return {"message": "Proposal is not fully funded"}, 400
    for ms in g.current_proposal.milestones:
        if ms.id == int(milestone_id):
            ms.reject_request(g.current_user.id, reason)
            db.session.add(ms)
            db.session.commit()
            # email TEAM that payout request was rejected
            for member in g.current_proposal.team:
                send_email(
                    member.email_address, 'milestone_reject', {
                        'proposal':
                        g.current_proposal,
                        'admin_note':
                        reason,
                        'proposal_milestones_url':
                        make_url(
                            f'/proposals/{g.current_proposal.id}?tab=milestones'
                        ),
                    })
            return proposal_schema.dump(g.current_proposal), 200

    return {"message": "No milestone matching id"}, 404
Ejemplo n.º 27
0
def post_proposal_team_invite(proposal_id, address):
    for u in g.current_proposal.team:
        if address == u.email_address:
            return {
                "message": f"Cannot invite members already on the team"
            }, 400

    existing_invite = ProposalTeamInvite.query.filter_by(
        proposal_id=proposal_id, address=address).first()
    if existing_invite:
        return {"message": f"You've already invited {address}"}, 400

    invite = ProposalTeamInvite(proposal_id=proposal_id, address=address)
    db.session.add(invite)
    db.session.commit()

    # Send email
    email = address
    user = User.get_by_email(email_address=address)
    if user:
        email = user.email_address
    if is_email(email):
        send_email(
            email, 'team_invite', {
                'user':
                user,
                'inviter':
                g.current_user,
                'proposal':
                g.current_proposal,
                'invite_url':
                make_url(
                    f'/profile/{user.id}?tab=invites' if user else '/auth')
            })

    return proposal_team_invite_schema.dump(invite), 201
Ejemplo n.º 28
0
from .subscription_settings import EmailSubscription, is_subscribed
from sendgrid.helpers.mail import Email, Mail, Content
from python_http_client import HTTPError
from grant.utils.misc import make_url
from sentry_sdk import capture_exception
from grant.settings import SENDGRID_API_KEY, SENDGRID_DEFAULT_FROM, SENDGRID_DEFAULT_FROMNAME, UI
from grant.settings import SENDGRID_API_KEY, SENDGRID_DEFAULT_FROM, UI, E2E_TESTING
import sendgrid
from threading import Thread
from flask import render_template, Markup, current_app, g


default_template_args = {
    'home_url': make_url('/'),
    'account_url': make_url('/profile'),
    'email_settings_url': make_url('/profile/settings?tab=emails'),
    'unsubscribe_url': make_url('/profile/settings?tab=emails'),
}


def signup_info(email_args):
    return {
        'subject': 'Confirm your email on {}'.format(UI['NAME']),
        'title': 'Welcome to {}!'.format(UI['NAME']),
        'preview': 'Welcome to {}, we just need to confirm your email address.'.format(UI['NAME']),
    }


def team_invite_info(email_args):
    return {
        'subject': '{} has invited you to a project'.format(email_args['inviter'].display_name),
Ejemplo n.º 29
0
def post_contribution_confirmation(contribution_id, to, amount, txid):
    contribution = ProposalContribution.query.filter_by(
        id=contribution_id).first()

    if not contribution:
        msg = f'Unknown contribution {contribution_id} confirmed with txid {txid}, amount {amount}'
        capture_message(msg)
        current_app.logger.warn(msg)
        return {"message": "No contribution matching id"}, 404

    if contribution.status == ContributionStatus.CONFIRMED:
        # Duplicates can happen, just return ok
        return {"message": "ok"}, 200

    # Convert to whole zcash coins from zats
    zec_amount = str(from_zat(int(amount)))

    contribution.confirm(tx_id=txid, amount=zec_amount)
    db.session.add(contribution)
    db.session.flush()

    if contribution.proposal.status == ProposalStatus.STAKING:
        contribution.proposal.set_pending_when_ready()

        # email progress of staking, partial or complete
        send_email(
            contribution.user.email_address, 'staking_contribution_confirmed',
            {
                'contribution': contribution,
                'proposal': contribution.proposal,
                'tx_explorer_url': make_explore_url(txid),
                'fully_staked': contribution.proposal.is_staked,
                'stake_target': str(PROPOSAL_STAKING_AMOUNT.normalize()),
            })

    else:
        # Send to the user
        if contribution.user:
            send_email(
                contribution.user.email_address, 'contribution_confirmed', {
                    'contribution': contribution,
                    'proposal': contribution.proposal,
                    'tx_explorer_url': make_explore_url(txid),
                })

        # Send to the full proposal gang
        for member in contribution.proposal.team:
            send_email(
                member.email_address, 'proposal_contribution', {
                    'proposal':
                    contribution.proposal,
                    'contribution':
                    contribution,
                    'contributor':
                    contribution.user,
                    'funded':
                    contribution.proposal.funded,
                    'proposal_url':
                    make_url(f'/proposals/{contribution.proposal.id}'),
                    'contributor_url':
                    make_url(f'/profile/{contribution.user.id}')
                    if contribution.user else '',
                })

    db.session.commit()
    return {"message": "ok"}, 200
Ejemplo n.º 30
0
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:
        return {"message": "No proposal matching id"}, 404

    if proposal.status != ProposalStatus.LIVE and proposal.status != ProposalStatus.DISCUSSION:
        return {
            "message":
            "Proposal must be live or open for public review to comment"
        }, 400

    # Make sure the parent comment exists
    parent = None
    if parent_comment_id:
        parent = Comment.query.filter_by(id=parent_comment_id).first()
        if not parent:
            return {"message": "Parent comment doesn’t exist"}, 400

    # Make sure user has verified their email
    if not g.current_user.email_verification.has_verified:
        return {"message": "Please confirm your email before commenting"}, 401

    # Make sure user is not silenced
    if g.current_user.silenced:
        return {
            "message":
            "Your account has been silenced, commenting is disabled."
        }, 403

    # 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)

    # Email proposal team if top-level comment
    if not parent:
        for member in proposal.team:
            send_email(
                member.email_address, 'proposal_comment', {
                    'author':
                    g.current_user,
                    'proposal':
                    proposal,
                    'comment_url':
                    make_url(
                        f'/proposals/{proposal.id}?tab=discussions&comment={comment.id}'
                    ),
                    'author_url':
                    make_url(f'/profile/{comment.author.id}'),
                })
    # Email parent comment creator, if it's not themselves
    if parent and parent.author.id != comment.author.id:
        send_email(
            parent.author.email_address, 'comment_reply', {
                'author':
                g.current_user,
                'proposal':
                proposal,
                'comment_url':
                make_url(
                    f'/proposals/{proposal.id}?tab=discussions&comment={comment.id}'
                ),
                'author_url':
                make_url(f'/profile/{comment.author.id}'),
            })

    return dumped_comment, 201