def files(): """ POST: upload attachment GET: get attachment status. """ from cloud_sql import sql_handler uid = flask.session.get('uid') data = flask.request.files logging.info(f'Receiving request {data} for session uid {uid}') access_token = sql_handler.get_nylas_access_token(uid) if not access_token: response = flask.jsonify({'status': 'no auth'}) response.status_code = 402 return response logging.info(f'Retrieved Nylas access token {access_token}') nylas = APIClient( app_id=app.config["NYLAS_OAUTH_CLIENT_ID"], app_secret=app.config["NYLAS_OAUTH_CLIENT_SECRET"], access_token=access_token ) if flask.request.method == 'POST': try: # check if the post request has the file part logging.info(f'{[key for key in flask.request.files.keys()]}') if 'file' not in flask.request.files and 'UploadFiles' not in flask.request.files: logging.info('No file part') return flask.redirect(flask.request.url) file = flask.request.files['UploadFiles'] or flask.request.files['file'] # if user does not select file, browser also # submit an empty part without filename if file.filename == '': logging.info('No selected file') return flask.redirect(flask.request.url) if file: filename = secure_filename(file.filename) logging.info(f'receiving file name {filename}') # file.save(os.path.join(app.config['UPLOAD_FOLDER'], filename)) nylas_file = nylas.files.create() nylas_file.filename = file.filename nylas_file.stream = file # .save() saves the file to Nylas, file.id can then be used to attach the file to an email nylas_file.save() response = flask.jsonify({'file_id': nylas_file.id}) response.status_code = 200 return response except Exception as e: logging.error(f'Uploading attachment failed! Error message is {str(e)}') response = flask.jsonify({'error': f'Uploading attachment failed! Error message is {str(e)}'}) response.status_code = 400 return response elif flask.request.method == 'GET': file_id = flask.request.args.get('file_id') if not file_id: response = flask.jsonify({'error': 'need valid file_id query param'}) response.status_code = 412 nylas_file = nylas.files.get(file_id) response = flask.jsonify({'file': nylas_file}) response.status_code = 200 return response
def send_email(): from cloud_sql import sql_handler uid = flask.session.get('uid') data = flask.request.json logging.info(f'Receiving request {data} for session uid {uid}') access_token = sql_handler.get_nylas_access_token(uid) if not access_token: response = flask.jsonify({'status': 'no auth'}) response.status_code = 402 return response logging.info(f'Retrieved Nylas access token {access_token}') nylas = APIClient( app_id=app.config["NYLAS_OAUTH_CLIENT_ID"], app_secret=app.config["NYLAS_OAUTH_CLIENT_SECRET"], access_token=access_token ) try: draft = nylas.drafts.create() draft.subject = data.get('subject') draft.body = data.get('body') draft.to = data.get('to') draft.cc = data.get('cc') draft.send() logging.info('email sent successfully') response = flask.jsonify('email sent successfully') response.status_code = 200 return response except Exception as e: logging.error(f'Sending email failed! Error message is {str(e)}') response = flask.jsonify(str(e)) response.status_code = 400 return response
def events(event_id): from cloud_sql import sql_handler if not event_id: logging.error('Need a valid event id') response = flask.jsonify('Need a valid event id') response.status_code = 400 return response uid = flask.session.get('uid') access_token = sql_handler.get_nylas_access_token(uid) if not access_token: logging.info(f'The account {uid} has not authorized yet') response = flask.jsonify({'status': 'no auth'}) response.status_code = 402 return response nylas_client = APIClient( app_id=app.config["NYLAS_OAUTH_CLIENT_ID"], app_secret=app.config["NYLAS_OAUTH_CLIENT_SECRET"], access_token=access_token ) try: if flask.request.method == "GET": event = nylas_client.events.get(event_id) response = flask.jsonify(event_to_dict(event)) response.status_code = 200 return response elif flask.request.method == "DELETE": nylas_client.events.delete(event_id, notify_participants='true') response = flask.jsonify('{Status: OK}') response.status_code = 200 return response elif flask.request.method == "PUT": data = flask.request.json event = nylas_client.events.where(event_id=event_id).first() if not event: response = flask.jsonify('unable to modify event') response.status_code = 400 return response event = update_event_from_json(event, data, event.calendar_id) event.save(notify_participants='true') logging.info('Calendar event updated successfully') response = flask.jsonify('Calendar event updated successfully') response.status_code = 200 return response except Exception as e: response = flask.jsonify(str(e)) response.status_code = 400 return response
def get_email_thread(): from cloud_sql import sql_handler uid = flask.session.get('uid') data = flask.request.json logging.info(f'Receiving request {data} for session uid {uid}') search_email = flask.request.args.get('email') sender_email = flask.session.get('email') if not search_email: response = flask.jsonify({'error': 'need valid search_email query param'}) response.status_code = 412 return response access_token = sql_handler.get_nylas_access_token(uid) if not access_token: response = flask.jsonify({'status': 'no auth'}) response.status_code = 402 return response logging.info(f'Retrieved Nylas access token {access_token}') nylas = APIClient( app_id=app.config["NYLAS_OAUTH_CLIENT_ID"], app_secret=app.config["NYLAS_OAUTH_CLIENT_SECRET"], access_token=access_token ) try: # Return all messages found in the user's inbox full_message = [] messages_to = nylas.messages.where(to=search_email) for m in messages_to: message = message_to_dict(m) if len(message['from']) > 0 and message['from'][0]['email'] == sender_email: full_message.append(message) messages_from = nylas.messages.where(from_=search_email) for m in messages_from: message = message_to_dict(m) if len(message['to']) > 0 and message['to'][0]['email'] == sender_email: full_message.append(message) full_message.sort(key=lambda x: x['received_at'], reverse=True) response = flask.jsonify(full_message[0:50]) response.status_code = 200 return response except Exception as e: logging.error(f'Sending email failed! Error message is {str(e)}') response = flask.jsonify(str(e)) response.status_code = 400 return response
def create_calendar_event(): from cloud_sql import sql_handler uid = flask.session.get('uid') data = flask.request.json logging.info(f'Receiving request {data} for session uid {uid}') access_token = sql_handler.get_nylas_access_token(uid) if not access_token: response = flask.jsonify({'status': 'no auth'}) response.status_code = 402 return response nylas = APIClient( app_id=app.config["NYLAS_OAUTH_CLIENT_ID"], app_secret=app.config["NYLAS_OAUTH_CLIENT_SECRET"], access_token=access_token ) # Most user accounts have multiple calendars where events are stored calendars = nylas.calendars.all() calendar_id = '' for calendar in calendars: # Print the name and description of each calendar and whether or not the calendar is read only logging.info("Name: {} | Description: {} | Read Only: {} | ID: {}".format( calendar.name, calendar.description, calendar.read_only, calendar.id)) if calendar.read_only: continue calendar_id = calendar.id logging.info(f'Found a valid writable calendar {calendar.name}') if not calendar_id: response = flask.jsonify({'status': 'No writable calendar found!'}) response.status_code = 400 return response # Create a new event event = nylas.events.create() event = update_event_from_json(event, data, calendar_id) # .save()must be called to save the event to the third party provider # The event object must have values assigned to calendar_id and when before you can save it. event.save(notify_participants='true') # notify_participants='true' will send a notification email to # all email addresses specified in the participant subobject logging.info('calendar event created successfully') response = flask.jsonify('calendar event created successfully') response.status_code = 200 return response
def send_email_util(subject, body, to_email, to_name, bcc_email='*****@*****.**', bcc_name='lifo customer support', uid=None): """ This method is a generic email sending util without attachments. """ from cloud_sql import sql_handler if not uid: uid = flask.session.get('uid') access_token = sql_handler.get_nylas_access_token(uid) if not access_token: response = flask.jsonify({'status': 'no auth'}) response.status_code = 402 return response logging.info(f'Retrieved Nylas access token') nylas = APIClient( app_id=app.config["NYLAS_OAUTH_CLIENT_ID"], app_secret=app.config["NYLAS_OAUTH_CLIENT_SECRET"], access_token=access_token ) try: draft = nylas.drafts.create() draft.subject = subject draft.to = [{'email': to_email, 'name': to_name}] # bcc our Lifo's internal support account to allow better tracking. draft.bcc = [{'email': bcc_email, 'name': bcc_name}] draft.body = body draft.tracking = {'links': 'true', 'opens': 'true', 'thread_replies': 'true', } draft.send() logging.info('email sent successfully') response = flask.jsonify('email sent successfully') response.status_code = 200 return response except Exception as e: logging.error(f'Sending email failed! Error message is {str(e)}') response = flask.jsonify(str(e)) response.status_code = 400 return response
def get_thread_by_id(): from cloud_sql import sql_handler uid = flask.session.get('uid') data = flask.request.json logging.info(f'Receiving request {data} for session uid {uid}') thread_id = flask.request.args.get('thread_id') if not thread_id: response = flask.jsonify({'error': 'need valid thread_id query param'}) response.status_code = 412 return response access_token = sql_handler.get_nylas_access_token(uid) if not access_token: response = flask.jsonify({'status': 'no auth'}) response.status_code = 402 return response logging.info(f'Retrieved Nylas access token {access_token}') nylas = APIClient( app_id=app.config["NYLAS_OAUTH_CLIENT_ID"], app_secret=app.config["NYLAS_OAUTH_CLIENT_SECRET"], access_token=access_token ) try: full_message = [] messages = nylas.messages.where(thread_id=thread_id) for m in messages: message = message_to_dict(m) full_message.append(message) full_message.sort(key=lambda x: x['received_at']) response = flask.jsonify(full_message) response.status_code = 200 return response except Exception as e: logging.error(f'Sending email failed! Error message is {str(e)}') response = flask.jsonify(str(e)) response.status_code = 400 return response
def revoke_auth(): """ This API essentially unauthorize and removes the customer from the Nylas authentication, along with auth for the Calendar and email accounts. """ from cloud_sql import sql_handler uid = flask.session['uid'] access_token = sql_handler.get_nylas_access_token(uid) if not access_token: logging.info(f'The account {uid} has not authorized yet') response = flask.jsonify({'status': 'no auth'}) response.status_code = 402 return response nylas_client = APIClient( app_id=app.config["NYLAS_OAUTH_CLIENT_ID"], app_secret=app.config["NYLAS_OAUTH_CLIENT_SECRET"], access_token=access_token, ) nylas_client.revoke_all_tokens() response = flask.jsonify('OK') response.status_code = 200 return response
def get_lifo_events(): """ Currently we rely on the hardcoded LIFO_CALENDAR_EVENT_SIGNATURE for events filtering. In future we may want to save all the events created by our product, and then get accordingly. But this will force us to fully manage the events life cycle. """ from cloud_sql import sql_handler uid = flask.session.get('uid') access_token = sql_handler.get_nylas_access_token(uid) if not access_token: logging.info(f'The account {uid} has not authorized yet') response = flask.jsonify({'status': 'no auth'}) response.status_code = 402 return response nylas_client = APIClient( app_id=app.config["NYLAS_OAUTH_CLIENT_ID"], app_secret=app.config["NYLAS_OAUTH_CLIENT_SECRET"], access_token=access_token ) events = nylas_client.events.where(description=LIFO_CALENDAR_EVENT_SIGNATURE) logging.info(f'Found events {str(events)}') response = flask.jsonify([event_to_dict(event) for event in events]) response.status_code = 200 return response
def send_single_email_with_template(): """ This method sends email with template, and replace: ${receiver_name} with to_name ${file_id} (if any) with file_id """ from cloud_sql import sql_handler uid = flask.session.get('uid') data = flask.request.json logging.info(f'Receiving request {data} for session uid {uid}') access_token = sql_handler.get_nylas_access_token(uid) if not access_token: response = flask.jsonify({'status': 'no auth'}) response.status_code = 402 return response logging.info(f'Retrieved nylas access token') nylas = APIClient( app_id=app.config["NYLAS_OAUTH_CLIENT_ID"], app_secret=app.config["NYLAS_OAUTH_CLIENT_SECRET"], access_token=access_token ) try: draft = nylas.drafts.create() if not data.get('subject') or not data.get('body') or not data.get('to_email') or not data.get('to_name'): response = flask.jsonify({'error': 'need valid file_id query param'}) response.status_code = 412 draft.subject = data.get('subject') body = data.get('body') #Note: should to_name be account_id instead? body = body.replace("$(receiver_name)", data.get('to_name')) # note: need to anonymize the email for external url usage. body = body.replace("$(inf_email)", data.get('to_email')) brand_campaign_id = data.get('brand_campaign_id') body = body.replace("$(brand_campaign_id)", brand_campaign_id) draft.to = [{'email': data.get('to_email'), 'name': data.get('to_name')}] # bcc our Lifo's internal support account to allow better tracking. draft.bcc = [{'email': '*****@*****.**', 'name': 'lifo customer support'}] if data.get('file_id'): file = nylas.files.get(data.get('file_id')) draft.attach(file) body = body.replace("$(file_id)", file.id) # when reply a thread if data.get('reply_to_message_id'): draft.reply_to_message_id = data.get('reply_to_message_id') draft.body = body draft.tracking = {'links': 'true', 'opens': 'true', 'thread_replies': 'true', 'payload': data.get('campaign_name') } """ Here we access influencer sub_collection under each brand campaign. This is to replicate the following function from Nodejs side under api_nodejs service. function access_influencer_subcollection(brand_campaign_id) { return db.collection(BRAND_CAMPAIGN_COLLECTIONS).doc(brand_campaign_id) .collection(INFLUENCER_COLLECTIONS); } """ inf_account_id = data.get('account_id') if inf_account_id != 'lifo' and data.get('to_name') != 'lifo' and 'lifo.ai' not in data.get('to_email'): emails_ref = db.collection(BRAND_CAMPAIGN_COLLECTIONS, brand_campaign_id, INFLUENCER_COLLECTIONS, inf_account_id, EMAILS_COLLECTIONS) hash_value = hashlib.sha256(body.encode('utf-8')).hexdigest() same_email_list = emails_ref.where('subject', u'==', draft.subject).where('body_hash', u'==', hash_value).get() logging.info(f'Found repeated email list {same_email_list}') if len(same_email_list) > 0: logging.info('Repeatedly sending the same emails!') response = flask.jsonify({'status': 'Repeated emails'}) response.status_code = 429 return response email_history = { 'ts': firestore.SERVER_TIMESTAMP, 'body': draft.body, 'body_hash': hash_value, 'subject': draft.subject, 'to': draft.to, 'file_id': data.get('file_id') } emails_ref.document().set(email_history) send_result = draft.send() logging.info(send_result) # Update influencer status influencer_ref = db.document(BRAND_CAMPAIGN_COLLECTIONS, brand_campaign_id, INFLUENCER_COLLECTIONS, inf_account_id) influencer_update_body = {} if data.get('for_contract'): influencer_update_body['inf_contract_status'] = 'Email sent' if not data.get('reply_to_message_id'): influencer_update_body['inf_contract_thread'] = send_result['thread_id'] influencer_update_body['contract_received_time'] = time.time() else: influencer_update_body['inf_contacting_status'] = 'Email sent' if not data.get('reply_to_message_id'): influencer_update_body['inf_offer_thread'] = send_result['thread_id'] influencer_update_body['offer_received_time'] = time.time() influencer_ref.set( influencer_update_body, merge=True ) logging.info('email sent successfully') response = flask.jsonify(send_result) response.status_code = 200 return response except Exception as e: logging.error(f'Sending email failed! Error message is {str(e)}') response = flask.jsonify(str(e)) response.status_code = 400 return response
def send_email_to_brand(): """ This method sends email with template, and replace: ${receiver_name} with to_name ${file_id} (if any) with file_id """ from cloud_sql import sql_handler uid = flask.session.get('uid') sender_name = flask.session.get('name') sender_email = flask.session.get('email') # Some user does not have 'name' field, add a temp hack here if not sender_name: sender_name = sender_email data = flask.request.json logging.info(f'Receiving request {data} for session uid {uid}') access_token = sql_handler.get_nylas_access_token(uid) if not access_token: response = flask.jsonify({'status': 'no auth'}) response.status_code = 402 return response logging.info(f'Retrieved nylas access token {access_token}') nylas = APIClient( app_id=app.config["NYLAS_OAUTH_CLIENT_ID"], app_secret=app.config["NYLAS_OAUTH_CLIENT_SECRET"], access_token=access_token ) try: draft = nylas.drafts.create() if not data.get('subject') or not data.get('body') or not data.get('to_email') or not data.get('to_name'): response = flask.jsonify({'error': 'need valid file_id query param'}) response.status_code = 412 draft.subject = data.get('subject') body = data.get('body') body = body.replace("$(receiver_name)", data.get('to_name')) draft.to = [{'email': data.get('to_email'), 'name': data.get('to_name')}] # bcc our Lifo's internal support account to allow better tracking. draft.bcc = [{'email': '*****@*****.**', 'name': 'lifo customer support'}] if data.get('file_id'): file = nylas.files.get(data.get('file_id')) draft.attach(file) body = body.replace("$(file_id)", file.id) draft.body = body draft.tracking = {'links': 'true', 'opens': 'true', 'thread_replies': 'true', 'payload': data.get('campaign_name') or f'{sender_name} <{sender_email}>' } brand_campaign_id = data.get('brand_campaign_id') emails_ref = db.collection(BRAND_CAMPAIGN_COLLECTIONS, brand_campaign_id, EMAILS_COLLECTIONS) email_history = { 'ts': firestore.SERVER_TIMESTAMP, 'body': draft.body, 'subject': draft.subject, 'to': draft.to, 'file_id': data.get('file_id') } emails_ref.document().set(email_history) draft.send() logging.info('email sent successfully') response = flask.jsonify('email sent successfully') response.status_code = 200 return response except Exception as e: logging.error(f'Sending email failed! Error message is {str(e)}') response = flask.jsonify(str(e)) response.status_code = 400 return response
def authorize(): """ This API authenticate for a chosen scope, and saves the nylas access code to cloud sql. Note: this API is the single entry point for authorization flow, and it will deactivate all previous access tokens for the given Nylas id. :return: flask response, 200 if success, 400 if failed. """ from cloud_sql import sql_handler nylas_code = flask.request.args.get('code') error = flask.request.args.get('error') uid = flask.session['uid'] logging.info(f'verifying auth sync status for uid {uid}') access_token = sql_handler.get_nylas_access_token(uid) # this is to handle potential authorization errors including access denied by customers. if error: logging.error(f'Authorization failed due to error: {error}') response = flask.jsonify(error) response.status_code = 400 return response nylas_client = APIClient(app_id=app.config["NYLAS_OAUTH_CLIENT_ID"], app_secret=app.config["NYLAS_OAUTH_CLIENT_SECRET"]) if not nylas_code: scope = flask.request.args.get('scope') if not scope: logging.error('Need a valid scope string, currently supporting: calendar,email.send,email.modify') scope = 'calendar,email.send,email.modify' redirect_url = 'http://auth.lifo.ai/authorize' logging.info(f'receiving request for scope {scope}, and redirect to {redirect_url}') flask.session['scope'] = scope # Redirect your user to the auth_url auth_url = nylas_client.authentication_url( redirect_url, scopes=scope ) return flask.redirect(auth_url) else: # note, here we have not handled the declined auth case logging.info(f'authentication success with code: {nylas_code}') access_token = nylas_client.token_for_code(nylas_code) nylas_client = APIClient( app_id=app.config["NYLAS_OAUTH_CLIENT_ID"], app_secret=app.config["NYLAS_OAUTH_CLIENT_SECRET"], access_token=access_token, ) # Revoke all tokens except for the new one nylas_client.revoke_all_tokens(keep_access_token=access_token) account = nylas_client.account nylas_account_id = account.id nylas_email = account.email_address uid = flask.session['uid'] scope = flask.session['scope'] logging.info(f'handling auth for firebase uid: {uid} for scope {scope} and nylas id {nylas_account_id}') # Note: here we automatically handle the Nylas id changing case, as each user is recognized by their firebase # uid, and any new Nylas ids will be overwritten. This does bring in limitations: a user can only authorize one # Calendar account, which is not an issue for foreseeable future. sql_handler.save_nylas_token(uid, nylas_access_token=access_token, nylas_account_id=nylas_account_id, email=nylas_email) return render_template("authorize_succeed.html")