Ejemplo n.º 1
0
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
Ejemplo n.º 2
0
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
Ejemplo n.º 3
0
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
Ejemplo n.º 4
0
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
Ejemplo n.º 5
0
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
Ejemplo n.º 6
0
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
Ejemplo n.º 7
0
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
Ejemplo n.º 8
0
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
Ejemplo n.º 9
0
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
Ejemplo n.º 10
0
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
Ejemplo n.º 11
0
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
Ejemplo n.º 12
0
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")