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