Ejemplo n.º 1
0
    def test_delete(self):
        """Method to test deleting a charity"""
        data = {
            "charity_id": "aclu",
            "campaigner_name": "John Doeski",
            "campaigner_email": "*****@*****.**",
            "match_cents": 5000
        }
        rv = self.app.post('/campaign', data=data, follow_redirects=True)

        self.assertEqual(rv.status_code, 201)
        response = json.loads(rv.data)
        assert "campaign_id" in response

        # Get charity object
        campaign_table = DynamoTable('campaigns')
        key = {"campaign_id": response["campaign_id"]}
        item = campaign_table.get_item(key)
        assert item is not None
        self.assertEqual(item["campaign_status"], "active")

        # Delete
        rv = self.app.post('/campaign/{}/cancel/{}'.format(
            response["campaign_id"], item["secret_id"]),
                           follow_redirects=True)
        self.assertEqual(rv.status_code, 204)

        # Verify it deleted
        item = campaign_table.get_item(key)
        self.assertEqual(item["campaign_status"], "cancelled")
Ejemplo n.º 2
0
    def test_delete_item(self):
        """Method to test receipt index"""
        campaign_table = DynamoTable('campaigns')

        # Add a record
        data = {
            "campaign_id": "my_campaign",
            "notified_on": arrow.utcnow().isoformat(),
            "campaign_status": "active"
        }
        campaign_table.put_item(data)

        # Verify write
        key = {"campaign_id": "my_campaign"}
        item = campaign_table.get_item(key)
        assert item is not None
        self.assertEqual(item["campaign_status"], "active")

        # Delete
        campaign_table.delete_item(key)

        # Verify it deleted
        item = campaign_table.get_item(key)
        assert item is None
Ejemplo n.º 3
0
class CampaignCancel(Resource):
    def __init__(self):
        super(Resource, self).__init__()
        self.campaign_table = DynamoTable('campaigns')

    @swagger.operation(notes='Cancel a campaign',
                       nickname='Cancel A Campaign',
                       parameters=[{
                           "name": "campaign_id",
                           "description": "UUID of the campaign",
                           "required": True,
                           "allowMultiple": False,
                           "dataType": 'string',
                           "paramType": "path"
                       }, {
                           "name": "secret_key",
                           "description":
                           "Secret key for the campaign to validate request",
                           "required": True,
                           "allowMultiple": False,
                           "dataType": 'string',
                           "paramType": "path"
                       }])
    def post(self, campaign_id, secret_key):
        """Cancel A Campaign"""
        # Get the campaign
        data = {"campaign_id": campaign_id}
        item = None
        try:
            item = self.campaign_table.get_item(data)
        except IOError as e:
            abort(400, description=e.message)

        if not item:
            abort(404,
                  description="Campaign '{}' not found".format(campaign_id))

        if item["secret_id"] == secret_key:
            # Update status to "cancelled"
            key = {"campaign_id": campaign_id}
            self.campaign_table.update_attribute(key, "campaign_status",
                                                 "cancelled")

        else:
            abort(403, description="Invalid Authorization Key")

        return None, 204
Ejemplo n.º 4
0
class Campaign(Resource):
    def __init__(self):
        super(Resource, self).__init__()
        self.table = DynamoTable('campaigns')

    @swagger.operation(notes='Service to create a new campaign',
                       nickname='Create a Campaign',
                       parameters=[{
                           "name":
                           "charity_id",
                           "description":
                           "Identifier of charity. Supported: {}".format(
                               ", ".join(
                                   [c["id"] for c in SUPPORTED_CHARITIES])),
                           "required":
                           True,
                           "allowMultiple":
                           False,
                           "dataType":
                           'string',
                           "paramType":
                           "query"
                       }, {
                           "name": "campaigner_name",
                           "description": "Campaigner's name",
                           "required": True,
                           "allowMultiple": False,
                           "dataType": 'string',
                           "paramType": "query"
                       }, {
                           "name": "campaigner_email",
                           "description": "Campaigner's email",
                           "required": True,
                           "allowMultiple": False,
                           "dataType": 'string',
                           "paramType": "query"
                       }, {
                           "name": "match_cents",
                           "description": "Target amount to match in cents",
                           "required": True,
                           "allowMultiple": False,
                           "dataType": 'integer',
                           "paramType": "query"
                       }])
    def post(self):
        """Create a Campaign"""
        args = story_post_parser.parse_args()

        # Verify charity is supported
        if args["charity_id"] not in [c["id"] for c in SUPPORTED_CHARITIES]:
            abort(400,
                  description="Unsupported charity: {}".format(
                      args["charity_id"]))

        # Create campaign id
        while True:
            u = uuid.uuid4()
            s = shortuuid.encode(u)[:5]
            if not self.table.get_item({"campaign_id": s}):
                break
        printable = " abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
        name_str = filter(lambda x: x in printable,
                          args["campaigner_name"].strip().lower())
        name_str = name_str.replace(" ", "-")

        args["campaign_id"] = "{}-{}".format(name_str, s)
        args["campaigner_name"] = args["campaigner_name"].strip()
        args["campaigner_email"] = args["campaigner_email"].strip()
        args["campaign_status"] = "active"
        args['notified_on'] = arrow.utcnow().isoformat()
        args['created_on'] = arrow.utcnow().isoformat()
        args['secret_id'] = shortuuid.uuid()
        args['charity_id'] = args["charity_id"]

        # Put the object
        self.table.put_item(args)

        # Notify the matcher
        dm_email = DonatematesEmail(
            campaigner_address=args["campaigner_email"])
        dm_email.send_campaign_created(args["campaign_id"], args["secret_id"])

        # Return
        return {"campaign_id": args["campaign_id"]}, 201
Ejemplo n.º 5
0
class CampaignProperties(Resource):
    def __init__(self):
        super(Resource, self).__init__()
        self.campaign_table = DynamoTable('campaigns')
        self.donation_table = DynamoTable('donations')

    @swagger.operation(notes='Get the properties of a campaign by ID',
                       nickname='Get Campaign Details',
                       parameters=[{
                           "name": "campaign_id",
                           "description": "UUID of the campaign",
                           "required": True,
                           "allowMultiple": False,
                           "dataType": 'string',
                           "paramType": "path"
                       }])
    def get(self, campaign_id):
        """Get Campaign Details"""
        # Get the campaign
        data = {"campaign_id": campaign_id}
        item = None
        try:
            item = self.campaign_table.get_item(data)
        except IOError as e:
            abort(400, description=e.message)

        if not item:
            abort(404,
                  description="Campaign '{}' not found".format(campaign_id))

        item["donation_email"] = "{}@donatemates.com".format(
            item["campaign_id"])

        # Get donor amount stats
        amounts = self.donation_table.query_biggest("campaign_id",
                                                    campaign_id,
                                                    5,
                                                    index="DonationIndex")

        # Get donor time stats
        donors = self.donation_table.query_most_recent(
            "campaign_id",
            campaign_id,
            "donation_on",
            arrow.utcnow().isoformat(),
            limit=5)

        item["large_donors"] = [{
            "donor_name": x["donor_name"],
            "donation_cents": float(x["donation_cents"])
        } for x in amounts]
        item["recent_donors"] = [{
            "donor_name": x["donor_name"],
            "donation_cents": float(x["donation_cents"])
        } for x in donors]

        # Sum donors
        item[
            "donation_total_cents"] = self.donation_table.integer_sum_attribute(
                "campaign_id", campaign_id, "donation_cents")

        # Get charity information
        charity = next(c for (i, c) in enumerate(SUPPORTED_CHARITIES)
                       if c["id"] == item["charity_id"])

        item["donation_url"] = charity["donation_url"]
        item["charity_name"] = charity["conversational_name"]
        del item["secret_id"]

        item = clean_dynamo_response(item)

        return item, 200

    def put(self, campaign_id):
        abort(403, description="Missing Authorization Key")

    def post(self, campaign_id):
        abort(403, description="Missing Authorization Key")
Ejemplo n.º 6
0
def process_email_handler(event, context):
    logger = logging.getLogger("boto3")
    logger.setLevel(logging.WARN)

    print("Received event: " + json.dumps(event, indent=2))

    # Check if S3 event or CloudWatch invocation. If just keeping things hot, exit.
    if "Records" in event:
        if "s3" in event["Records"][0]:
            key = event["Records"][0]["s3"]["object"]["key"]
            bucket = event["Records"][0]["s3"]["bucket"]["name"]
    else:
        print("Not an email. Move along...")
        return

    # Load message from S3
    s3 = boto3.resource('s3')
    email_obj = s3.Object(bucket, key)
    email_mime = email_obj.get()['Body'].read().decode('utf-8')

    # Detect Charity
    for sup_charity in SUPPORTED_CHARITIES:
        class_ = getattr(charity, sup_charity["class"])
        charity_class = class_(email_mime)

        # Detect charity
        if charity_class.is_receipt():
            # Found the charity

            # Get campaign ID and campaign data
            campaign_id = charity_class.get_campaign_id()
            print("CAMPAIGN ID: {}".format(campaign_id))
            campaign_table = DynamoTable('campaigns')
            campaign_key = {"campaign_id": campaign_id}
            campaign = campaign_table.get_item(campaign_key)

            if not campaign:
                print("WARNING: **** CAMPAIGN DOES NOT EXIST ****")
                dm_email = DonatematesEmail(charity_class.from_email)
                dm_email.send_campaign_does_not_exist()
                return True

            # Setup email sender
            dm_email = DonatematesEmail(charity_class.from_email,
                                        campaign["campaigner_email"])

            # Get donation receipt
            data = charity_class.parse_email()
            data["receipt_id"] = data["receipt_id"].strip()

            # Validate this is a new donation
            donation_table = DynamoTable('donations')
            existing_receipts = donation_table.query_hash("receipt_id",
                                                          data["receipt_id"],
                                                          index="ReceiptIndex",
                                                          limit=10)
            if existing_receipts:
                # This receipt already exists!
                print(
                    "WARNING: **** Duplicate receipt detected - Campaign: {} - Receipt: {} - Bucket: {} - Key: {} ****"
                    .format(campaign_id, data["receipt_id"], bucket, key))
                # Notify user we didn't process it
                dm_email.send_duplicate_receipt(campaign_id,
                                                data["receipt_id"], key)
                return True

            # Add donation record
            data["campaign_id"] = campaign_id
            data["donation_on"] = arrow.utcnow().isoformat()
            data["email_bucket"] = bucket
            data["email_key"] = key
            print("DONATION DATA:")
            print(data)
            store_donation(data)

            # Get updated total donation
            donation_total_cents = donation_table.integer_sum_attribute(
                "campaign_id", campaign_id, "donation_cents")

            # Notify the Donor
            if campaign["campaign_status"] == "cancelled":
                # If cancelled, only notify donor and let them know the campaign isn't going on.
                dm_email.send_campaign_cancelled()
            else:
                # Send standard confirmation to donor
                dm_email.send_donation_confirmation(data["donation_cents"])

            # Notify the campaigner if the campaign is active only
            if campaign["campaign_status"] == "active":
                # Update notification time (for future possible digest emails)
                campaign_table.update_attribute(campaign_key, "notified_on",
                                                arrow.utcnow().isoformat())

                if donation_total_cents >= campaign["match_cents"]:
                    # Update campaign status to "matched"
                    campaign_table.update_attribute(campaign_key,
                                                    "campaign_status",
                                                    "matched")

                    # Send campaign completion email!
                    dm_email.send_campaign_matched(data["donor_name"],
                                                   data["donation_cents"],
                                                   donation_total_cents,
                                                   campaign["match_cents"])
                else:
                    # Send normal update
                    dm_email.send_campaign_update(data["donor_name"],
                                                  data["donation_cents"],
                                                  donation_total_cents,
                                                  campaign["match_cents"])

            # Exit
            return True

    # If you get here, you didn't successfully parse the email or it was unsupported
    # Save email to error bucket
    s3.Object('parse-fail-donatemates', '{}'.format(
        shortuuid.uuid())).copy_from(CopySource='{}/{}'.format(bucket, key))

    # Reply to user
    print(
        "WARNING: **** Failed to detect a supported charity - Email Key: {} ****"
        .format(key))