def draft_update_api(public_id): data = request.get_json(force=True) original_draft = get_draft(public_id, data.get('version'), g.namespace.id, g.db_session) # TODO(emfree): what if you try to update a draft on a *thread* that's been # deleted? data = request.get_json(force=True) to = get_recipients(data.get('to'), 'to') cc = get_recipients(data.get('cc'), 'cc') bcc = get_recipients(data.get('bcc'), 'bcc') from_addr = get_recipients(data.get('from_addr'), 'from_addr') reply_to = get_recipients(data.get('reply_to'), 'reply_to') if from_addr and len(from_addr) > 1: raise InputError("from_addr field can have at most one item") if reply_to and len(reply_to) > 1: raise InputError("reply_to field can have at most one item") subject = data.get('subject') body = data.get('body') tags = get_tags(data.get('tags'), g.namespace.id, g.db_session) files = get_attachments(data.get('file_ids'), g.namespace.id, g.db_session) draft = update_draft(g.db_session, g.namespace.account, original_draft, to, subject, body, files, cc, bcc, from_addr, reply_to, tags) return g.encoder.jsonify(draft)
def draft_update_api(public_id): data = request.get_json(force=True) original_draft = get_draft(public_id, data.get('version'), g.namespace.id, g.db_session) # TODO(emfree): what if you try to update a draft on a *thread* that's been # deleted? data = request.get_json(force=True) to = get_recipients(data.get('to'), 'to') cc = get_recipients(data.get('cc'), 'cc') bcc = get_recipients(data.get('bcc'), 'bcc') from_addr = get_recipients(data.get('from_addr'), 'from_addr') reply_to = get_recipients(data.get('reply_to'), 'reply_to') if from_addr and len(from_addr) > 1: raise InputError("from_addr field can have at most one item") if reply_to and len(reply_to) > 1: raise InputError("reply_to field can have at most one item") subject = data.get('subject') body = data.get('body') files = get_attachments(data.get('file_ids'), g.namespace.id, g.db_session) draft = update_draft(g.db_session, g.namespace.account, original_draft, to, subject, body, files, cc, bcc, from_addr, reply_to) return g.encoder.jsonify(draft)
def draft_create_api(): data = request.get_json(force=True) to = get_recipients(data.get('to'), 'to') cc = get_recipients(data.get('cc'), 'cc') bcc = get_recipients(data.get('bcc'), 'bcc') subject = data.get('subject') body = data.get('body') try: tags = get_tags(data.get('tags'), g.namespace.id, g.db_session) files = get_attachments(data.get('file_ids'), g.namespace.id, g.db_session) replyto_thread = get_thread(data.get('thread_id'), g.namespace.id, g.db_session) except InputError as e: return err(404, e.message) try: draft = sendmail.create_draft(g.db_session, g.namespace.account, to, subject, body, files, cc, bcc, tags, replyto_thread) except ActionError as e: return err(e.error, str(e)) return g.encoder.jsonify(draft)
def draft_send_api(): data = request.get_json(force=True) draft_public_id = data.get('draft_id') if draft_public_id is not None: draft = get_draft(draft_public_id, data.get('version'), g.namespace.id, g.db_session) if not any((draft.to_addr, draft.cc_addr, draft.bcc_addr)): raise InputError('No recipients specified') validate_draft_recipients(draft) resp = send_draft(g.namespace.account, draft, g.db_session, schedule_remote_delete=True) else: to = get_recipients(data.get('to'), 'to', validate_emails=True) cc = get_recipients(data.get('cc'), 'cc', validate_emails=True) bcc = get_recipients(data.get('bcc'), 'bcc', validate_emails=True) if not any((to, cc, bcc)): raise InputError('No recipients specified') subject = data.get('subject') body = data.get('body') tags = get_tags(data.get('tags'), g.namespace.id, g.db_session) files = get_attachments(data.get('file_ids'), g.namespace.id, g.db_session) replyto_thread = get_thread(data.get('thread_id'), g.namespace.id, g.db_session) draft = sendmail.create_draft(g.db_session, g.namespace.account, to, subject, body, files, cc, bcc, tags, replyto_thread, syncback=False) resp = send_draft(g.namespace.account, draft, g.db_session, schedule_remote_delete=False) return resp
def draft_update_api(public_id): data = request.get_json(force=True) original_draft = get_draft(public_id, data.get('version'), g.namespace.id, g.db_session) # TODO(emfree): what if you try to update a draft on a *thread* that's been # deleted? data = request.get_json(force=True) to = get_recipients(data.get('to'), 'to') cc = get_recipients(data.get('cc'), 'cc') bcc = get_recipients(data.get('bcc'), 'bcc') subject = data.get('subject') body = data.get('body') tags = get_tags(data.get('tags'), g.namespace.id, g.db_session) files = get_attachments(data.get('file_ids'), g.namespace.id, g.db_session) try: draft = sendmail.update_draft(g.db_session, g.namespace.account, original_draft, to, subject, body, files, cc, bcc, tags) except ActionError as e: return err(e.error, str(e)) return g.encoder.jsonify(draft)
def draft_update_api(public_id): try: valid_public_id(public_id) except InputError: return err(400, 'Invalid draft id {}'.format(public_id)) data = request.get_json(force=True) if data.get('version') is None: return err(400, 'Must specify version to update') version = data.get('version') original_draft = g.db_session.query(Message).filter( Message.public_id == public_id).first() if original_draft is None or not original_draft.is_draft or \ original_draft.namespace.id != g.namespace.id: return err(404, 'No draft with public id {}'.format(public_id)) if original_draft.version != version: return err( 409, 'Draft {0}.{1} has already been updated to version ' '{2}'.format(public_id, version, original_draft.version)) # TODO(emfree): what if you try to update a draft on a *thread* that's been # deleted? data = request.get_json(force=True) to = get_recipients(data.get('to'), 'to') cc = get_recipients(data.get('cc'), 'cc') bcc = get_recipients(data.get('bcc'), 'bcc') subject = data.get('subject') body = data.get('body') try: tags = get_tags(data.get('tags'), g.namespace.id, g.db_session) files = get_attachments(data.get('file_ids'), g.namespace.id, g.db_session) except InputError as e: return err(404, e.message) try: draft = sendmail.update_draft(g.db_session, g.namespace.account, original_draft, to, subject, body, files, cc, bcc, tags) except ActionError as e: return err(e.error, str(e)) return g.encoder.jsonify(draft)
def draft_update_api(public_id): try: valid_public_id(public_id) except InputError: return err(400, 'Invalid draft id {}'.format(public_id)) data = request.get_json(force=True) if data.get('version') is None: return err(400, 'Must specify version to update') version = data.get('version') original_draft = g.db_session.query(Message).filter( Message.public_id == public_id).first() if original_draft is None or not original_draft.is_draft or \ original_draft.namespace.id != g.namespace.id: return err(404, 'No draft with public id {}'.format(public_id)) if original_draft.version != version: return err(409, 'Draft {0}.{1} has already been updated to version ' '{2}'.format(public_id, version, original_draft.version)) # TODO(emfree): what if you try to update a draft on a *thread* that's been # deleted? data = request.get_json(force=True) to = get_recipients(data.get('to'), 'to') cc = get_recipients(data.get('cc'), 'cc') bcc = get_recipients(data.get('bcc'), 'bcc') subject = data.get('subject') body = data.get('body') try: tags = get_tags(data.get('tags'), g.namespace.id, g.db_session) files = get_attachments(data.get('file_ids'), g.namespace.id, g.db_session) except InputError as e: return err(404, e.message) try: draft = sendmail.update_draft(g.db_session, g.namespace.account, original_draft, to, subject, body, files, cc, bcc, tags) except ActionError as e: return err(e.error, str(e)) return g.encoder.jsonify(draft)
def create_message_from_json(data, namespace, db_session, is_draft): """ Construct a Message instance from `data`, a dictionary representing the POST body of an API request. All new objects are added to the session, but not committed.""" # Validate the input and get referenced objects (thread, attachments) # as necessary. to_addr = get_recipients(data.get('to'), 'to') cc_addr = get_recipients(data.get('cc'), 'cc') bcc_addr = get_recipients(data.get('bcc'), 'bcc') from_addr = get_recipients(data.get('from'), 'from') reply_to = get_recipients(data.get('reply_to'), 'reply_to') if from_addr and len(from_addr) > 1: raise InputError("from_addr field can have at most one item") if reply_to and len(reply_to) > 1: raise InputError("reply_to field can have at most one item") subject = data.get('subject') if subject is not None and not isinstance(subject, basestring): raise InputError('"subject" should be a string') body = data.get('body', '') if not isinstance(body, basestring): raise InputError('"body" should be a string') blocks = get_attachments(data.get('file_ids'), namespace.id, db_session) reply_to_thread = get_thread(data.get('thread_id'), namespace.id, db_session) reply_to_message = get_message(data.get('reply_to_message_id'), namespace.id, db_session) if reply_to_message is not None and reply_to_thread is not None: if reply_to_message not in reply_to_thread.messages: raise InputError('Message {} is not in thread {}'.format( reply_to_message.public_id, reply_to_thread.public_id)) with db_session.no_autoflush: account = namespace.account dt = datetime.utcnow() uid = generate_public_id() to_addr = to_addr or [] cc_addr = cc_addr or [] bcc_addr = bcc_addr or [] blocks = blocks or [] if subject is None: # If this is a reply with no explicitly specified subject, set the # subject from the prior message/thread by default. # TODO(emfree): Do we want to allow changing the subject on a reply # at all? if reply_to_message is not None: subject = reply_to_message.subject elif reply_to_thread is not None: subject = reply_to_thread.subject subject = subject or '' message = Message() message.namespace = namespace message.is_created = True message.is_draft = is_draft message.from_addr = from_addr if from_addr else \ [(account.name, account.email_address)] # TODO(emfree): we should maybe make received_date nullable, so its # value doesn't change in the case of a drafted-and-later-reconciled # message. message.received_date = dt message.subject = subject message.body = body message.to_addr = to_addr message.cc_addr = cc_addr message.bcc_addr = bcc_addr message.reply_to = reply_to # TODO(emfree): this is different from the normal 'size' value of a # message, which is the size of the entire MIME message. message.size = len(body) message.is_read = True message.is_sent = False message.public_id = uid message.version = 0 message.regenerate_inbox_uid() # Set the snippet message.snippet = message.calculate_html_snippet(body) # Associate attachments to the draft message for block in blocks: # Create a new Part object to associate to the message object. # (You can't just set block.message, because if block is an # attachment on an existing message, that would dissociate it from # the existing message.) part = Part(block=block) part.namespace_id = namespace.id part.content_disposition = 'attachment' part.is_inboxapp_attachment = True message.parts.append(part) update_contacts_from_message(db_session, message, namespace) if reply_to_message is not None: message.is_reply = True _set_reply_headers(message, reply_to_message) thread = reply_to_message.thread message.reply_to_message = reply_to_message elif reply_to_thread is not None: message.is_reply = True thread = reply_to_thread # Construct the in-reply-to and references headers from the last # message currently in the thread. previous_messages = [m for m in thread.messages if not m.is_draft] if previous_messages: last_message = previous_messages[-1] message.reply_to_message = last_message _set_reply_headers(message, last_message) else: # If this isn't a reply to anything, create a new thread object for # the draft. We specialize the thread class so that we can, for # example, add the g_thrid for Gmail later if we reconcile a synced # message with this one. This is a huge hack, but works. message.is_reply = False thread_cls = account.thread_cls thread = thread_cls(subject=message.subject, recentdate=message.received_date, namespace=namespace, subjectdate=message.received_date) message.thread = thread db_session.add(message) if is_draft: schedule_action('save_draft', message, namespace.id, db_session, version=message.version) db_session.flush() return message
def draft_send_api(): data = request.get_json(force=True) if data.get('draft_id') is None: if data.get('to') is None: return err(400, 'Must specify either draft id + version or ' 'message recipients.') else: if data.get('version') is None: return err(400, 'Must specify version to send') draft_public_id = data.get('draft_id') version = data.get('version') if draft_public_id is not None: try: valid_public_id(draft_public_id) draft = g.db_session.query(Message).filter( Message.public_id == draft_public_id).one() except InputError: return err(400, 'Invalid public id {}'.format(draft_public_id)) except NoResultFound: return err(404, 'No draft found with id {}'. format(draft_public_id)) if draft.namespace != g.namespace: return err(404, 'No draft found with id {}'. format(draft_public_id)) if draft.is_sent or not draft.is_draft: return err(400, 'Message with id {} is not a draft'. format(draft_public_id)) if not draft.to_addr: return err(400, "No 'to:' recipients specified") if draft.version != version: return err( 409, 'Draft {0}.{1} has already been updated to version {2}'. format(draft_public_id, version, draft.version)) schedule_action('send_draft', draft, g.namespace.id, g.db_session) else: to = get_recipients(data.get('to'), 'to', validate_emails=True) cc = get_recipients(data.get('cc'), 'cc', validate_emails=True) bcc = get_recipients(data.get('bcc'), 'bcc', validate_emails=True) subject = data.get('subject') body = data.get('body') try: tags = get_tags(data.get('tags'), g.namespace.id, g.db_session) files = get_attachments(data.get('file_ids'), g.namespace.id, g.db_session) replyto_thread = get_thread(data.get('thread_id'), g.namespace.id, g.db_session) except InputError as e: return err(404, e.message) draft = sendmail.create_draft(g.db_session, g.namespace.account, to, subject, body, files, cc, bcc, tags, replyto_thread, syncback=False) schedule_action('send_directly', draft, g.namespace.id, g.db_session) draft.state = 'sending' return g.encoder.jsonify(draft)
def create_draft(data, namespace, db_session, syncback): """ Construct a draft object (a Message instance) from `data`, a dictionary representing the POST body of an API request. All new objects are added to the session, but not committed.""" # Validate the input and get referenced objects (thread, attachments) # as necessary. to_addr = get_recipients(data.get('to'), 'to') cc_addr = get_recipients(data.get('cc'), 'cc') bcc_addr = get_recipients(data.get('bcc'), 'bcc') from_addr = get_recipients(data.get('from'), 'from') reply_to = get_recipients(data.get('reply_to'), 'reply_to') if from_addr and len(from_addr) > 1: raise InputError("from_addr field can have at most one item") if reply_to and len(reply_to) > 1: raise InputError("reply_to field can have at most one item") subject = data.get('subject') if subject is not None and not isinstance(subject, basestring): raise InputError('"subject" should be a string') body = data.get('body', '') if not isinstance(body, basestring): raise InputError('"body" should be a string') blocks = get_attachments(data.get('file_ids'), namespace.id, db_session) reply_to_thread = get_thread(data.get('thread_id'), namespace.id, db_session) reply_to_message = get_message(data.get('reply_to_message_id'), namespace.id, db_session) if reply_to_message is not None and reply_to_thread is not None: if reply_to_message not in reply_to_thread.messages: raise InputError('Message {} is not in thread {}'. format(reply_to_message.public_id, reply_to_thread.public_id)) with db_session.no_autoflush: account = namespace.account dt = datetime.utcnow() uid = generate_public_id() to_addr = to_addr or [] cc_addr = cc_addr or [] bcc_addr = bcc_addr or [] blocks = blocks or [] if subject is None: # If this is a reply with no explicitly specified subject, set the # subject from the prior message/thread by default. # TODO(emfree): Do we want to allow changing the subject on a reply # at all? if reply_to_message is not None: subject = reply_to_message.subject elif reply_to_thread is not None: subject = reply_to_thread.subject subject = subject or '' message = Message() message.namespace = namespace message.is_created = True message.is_draft = True message.from_addr = from_addr if from_addr else \ [(account.name, account.email_address)] # TODO(emfree): we should maybe make received_date nullable, so its # value doesn't change in the case of a drafted-and-later-reconciled # message. message.received_date = dt message.subject = subject message.body = body message.to_addr = to_addr message.cc_addr = cc_addr message.bcc_addr = bcc_addr message.reply_to = reply_to # TODO(emfree): this is different from the normal 'size' value of a # message, which is the size of the entire MIME message. message.size = len(body) message.is_read = True message.is_sent = False message.public_id = uid message.version = 0 message.regenerate_inbox_uid() # Set the snippet message.snippet = message.calculate_html_snippet(body) # Associate attachments to the draft message for block in blocks: # Create a new Part object to associate to the message object. # (You can't just set block.message, because if block is an # attachment on an existing message, that would dissociate it from # the existing message.) part = Part(block=block) part.namespace_id = namespace.id part.content_disposition = 'attachment' part.is_inboxapp_attachment = True message.parts.append(part) update_contacts_from_message(db_session, message, namespace) if reply_to_message is not None: message.is_reply = True _set_reply_headers(message, reply_to_message) thread = reply_to_message.thread message.reply_to_message = reply_to_message elif reply_to_thread is not None: message.is_reply = True thread = reply_to_thread # Construct the in-reply-to and references headers from the last # message currently in the thread. previous_messages = [m for m in thread.messages if not m.is_draft] if previous_messages: last_message = previous_messages[-1] message.reply_to_message = last_message _set_reply_headers(message, last_message) else: # If this isn't a reply to anything, create a new thread object for # the draft. We specialize the thread class so that we can, for # example, add the g_thrid for Gmail later if we reconcile a synced # message with this one. This is a huge hack, but works. message.is_reply = False thread_cls = account.thread_cls thread = thread_cls( subject=message.subject, recentdate=message.received_date, namespace=namespace, subjectdate=message.received_date) message.thread = thread db_session.add(message) if syncback: schedule_action('save_draft', message, namespace.id, db_session, version=message.version) db_session.flush() return message
def draft_send_api(): data = request.get_json(force=True) if data.get('draft_id') is None: if data.get('to') is None: return err( 400, 'Must specify either draft id + version or ' 'message recipients.') else: if data.get('version') is None: return err(400, 'Must specify version to send') draft_public_id = data.get('draft_id') version = data.get('version') if draft_public_id is not None: try: valid_public_id(draft_public_id) draft = g.db_session.query(Message).filter( Message.public_id == draft_public_id).one() except InputError: return err(400, 'Invalid public id {}'.format(draft_public_id)) except NoResultFound: return err(404, 'No draft found with id {}'.format(draft_public_id)) if draft.namespace != g.namespace: return err(404, 'No draft found with id {}'.format(draft_public_id)) if draft.is_sent or not draft.is_draft: return err( 400, 'Message with id {} is not a draft'.format(draft_public_id)) if not draft.to_addr: return err(400, "No 'to:' recipients specified") if draft.version != version: return err( 409, 'Draft {0}.{1} has already been updated to version {2}'.format( draft_public_id, version, draft.version)) validate_draft_recipients(draft) try: schedule_action('send_draft', draft, g.namespace.id, g.db_session) except ActionError as e: return err(e.error, str(e)) else: to = get_recipients(data.get('to'), 'to', validate_emails=True) cc = get_recipients(data.get('cc'), 'cc', validate_emails=True) bcc = get_recipients(data.get('bcc'), 'bcc', validate_emails=True) subject = data.get('subject') body = data.get('body') try: tags = get_tags(data.get('tags'), g.namespace.id, g.db_session) files = get_attachments(data.get('file_ids'), g.namespace.id, g.db_session) replyto_thread = get_thread(data.get('thread_id'), g.namespace.id, g.db_session) except InputError as e: return err(404, e.message) try: draft = sendmail.create_draft(g.db_session, g.namespace.account, to, subject, body, files, cc, bcc, tags, replyto_thread, syncback=False) schedule_action('send_directly', draft, g.namespace.id, g.db_session) except ActionError as e: return err(e.error, str(e)) draft.state = 'sending' return g.encoder.jsonify(draft)