def message_api(public_id): try: valid_public_id(public_id) message = g.db_session.query(Message).filter( Message.public_id == public_id).one() assert int(message.namespace.id) == int(g.namespace.id) except InputError: return err(400, 'Invalid message id {}'.format(public_id)) except NoResultFound: return err( 404, "Couldn't find message with id {0} " "on namespace {1}".format(public_id, g.namespace_public_id)) if request.method == 'GET': return g.encoder.jsonify(message) elif request.method == 'PUT': data = request.get_json(force=True) if data.keys() != ['unread'] or not isinstance(data['unread'], bool): return err(400, 'Can only change the unread attribute of a message') # TODO(emfree): Shouldn't allow this on messages that are actually # drafts. unread_tag = message.namespace.tags['unread'] unseen_tag = message.namespace.tags['unseen'] if data['unread']: message.is_read = False message.thread.apply_tag(unread_tag) else: message.is_read = True message.thread.remove_tag(unseen_tag) if all(m.is_read for m in message.thread.messages): message.thread.remove_tag(unread_tag) return g.encoder.jsonify(message)
def draft_update_api(public_id): try: valid_public_id(public_id) except InputError: return err(400, 'Invalid draft id {}'.format(public_id)) parent_draft = g.db_session.query(Message). \ filter(Message.public_id == public_id).first() if parent_draft is None or not parent_draft.is_draft or \ parent_draft.namespace.id != g.namespace.id: return err(404, 'No draft with public id {}'.format(public_id)) if not parent_draft.is_latest: return err(409, 'Draft {} has already been updated to {}'.format( public_id, g.encoder.cereal(parent_draft.most_recent_revision))) # TODO(emfree): what if you try to update a draft on a *thread* that's been # deleted? data = request.get_json(force=True) to = data.get('to') cc = data.get('cc') bcc = data.get('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('files'), g.namespace.id, g.db_session) except InputError as e: return err(404, e.message) draft = sendmail.update_draft(g.db_session, g.namespace.account, parent_draft, to, subject, body, files, cc, bcc, tags) return g.encoder.jsonify(draft)
def webhooks_read_update_api(public_id): if request.method == 'GET': try: hook = g.db_session.query(Webhook).filter( Webhook.public_id == public_id, Webhook.namespace_id == g.namespace.id).one() return jsonify(hook) except NoResultFound: return err(404, "Couldn't find webhook with id {}".format(public_id)) if request.method == 'PUT': data = request.get_json(force=True) # We only support updates to the 'active' flag. if data.keys() != ['active']: return err(400, 'Malformed webhook request') try: if data['active']: get_webhook_client().start_hook(public_id) else: get_webhook_client().stop_hook(public_id) return jsonify({"success": True}) except zerorpc.RemoteError: return err(404, "Couldn't find webhook with id {}".format(public_id))
def event_create_api(): # Handle ical uploads if request.headers['content-type'] == 'text/calendar': ics_str = request.data new_events = events.crud.create_from_ics(g.namespace, g.db_session, ics_str) if not new_events: return err(400, "Couldn't parse .ics file.") return g.encoder.jsonify(new_events) data = request.get_json(force=True) try: valid_event(data) except InputError as e: return err(404, e.message) subject = data.get('subject', '') body = data.get('body') location = data.get('location') reminders = data.get('reminders') recurrence = data.get('recurrence') when = data.get('when') participants = data.get('participants', []) for p in participants: if 'status' not in p: p['status'] = 'noreply' new_contact = events.crud.create(g.namespace, g.db_session, subject, body, location, reminders, recurrence, when, participants) return g.encoder.jsonify(new_contact)
def event_update_api(public_id): data = request.get_json(force=True) try: valid_event_update(data) except InputError as e: return err(404, e.message) if 'start' in data: data['start'] = datetime.utcfromtimestamp(int(data.get('start'))) if 'end' in data: data['end'] = datetime.utcfromtimestamp(int(data.get('end'))) if 'busy' in data: data['busy'] = int(data.get('busy')) if 'all_day' in data: data['all_day'] = int(data.get('all_day')) if 'participants' in data: data['participant_list'] = data['participants'] del data['participants'] for p in data['participant_list']: if 'status' not in p: p['status'] = 'awaiting' result = events.crud.update(g.namespace, g.db_session, public_id, data) if result is None: return err(404, "Couldn't find event with id {0}". format(public_id)) return g.encoder.jsonify(result)
def start(): g.db_session = InboxSession() g.log = current_app.logger try: g.namespace = g.db_session.query(Namespace) \ .filter(Namespace.public_id == g.namespace_public_id).one() except NoResultFound: return err( 404, "Couldn't find namespace with id `{0}` ".format( g.namespace_public_id)) try: g.lens = Lens( namespace_id=g.namespace.id, subject=request.args.get('subject'), thread_public_id=request.args.get('thread'), to_addr=request.args.get('to'), from_addr=request.args.get('from'), cc_addr=request.args.get('cc'), bcc_addr=request.args.get('bcc'), any_email=request.args.get('any_email'), started_before=request.args.get('started_before'), started_after=request.args.get('started_after'), last_message_before=request.args.get('last_message_before'), last_message_after=request.args.get('last_message_after'), filename=request.args.get('filename'), tag=request.args.get('tag'), detached=True) g.lens_limit = request.args.get('limit') g.lens_offset = request.args.get('offset') except ValueError as e: return err(400, e.message)
def event_update_api(public_id): try: valid_public_id(public_id) except InputError: return err(400, 'Invalid event id {}'.format(public_id)) data = request.get_json(force=True) try: valid_event_update(data, g.namespace, g.db_session) except InputError as e: return err(404, e.message) # Convert the data into our types where necessary # e.g. timestamps, participant_list if 'start' in data: data['start'] = datetime.utcfromtimestamp(int(data.get('start'))) if 'end' in data: data['end'] = datetime.utcfromtimestamp(int(data.get('end'))) if 'participants' in data: data['participant_list'] = [] for p in data['participants']: if 'status' not in p: p['status'] = 'noreply' data['participant_list'].append(p) del data['participants'] try: result = events.crud.update(g.namespace, g.db_session, public_id, data) except InputError as e: return err(400, e.message) if result is None: return err(404, "Couldn't find event with id {0}".format(public_id)) return g.encoder.jsonify(result)
def start(): g.db_session = InboxSession() g.log = current_app.logger try: g.namespace = g.db_session.query(Namespace) \ .filter(Namespace.public_id == g.namespace_public_id).one() except NoResultFound: return err(404, "Couldn't find namespace with id `{0}` ".format( g.namespace_public_id)) try: g.lens = Lens( namespace_id=g.namespace.id, subject=request.args.get('subject'), thread_public_id=request.args.get('thread'), to_addr=request.args.get('to'), from_addr=request.args.get('from'), cc_addr=request.args.get('cc'), bcc_addr=request.args.get('bcc'), any_email=request.args.get('any_email'), started_before=request.args.get('started_before'), started_after=request.args.get('started_after'), last_message_before=request.args.get('last_message_before'), last_message_after=request.args.get('last_message_after'), filename=request.args.get('filename'), tag=request.args.get('tag'), detached=True) g.lens_limit = request.args.get('limit') g.lens_offset = request.args.get('offset') except ValueError as e: return err(400, e.message)
def contact_search_api(): filter = request.args.get('filter', '') try: limit = int(request.args.get('limit', 10)) offset = int(request.args.get('offset', 0)) except ValueError: return err(400, 'limit and offset parameters must be integers') if limit < 0 or offset < 0: return err( 400, 'limit and offset parameters must be nonnegative ' 'integers') if limit > 1000: return err(400, 'cannot request more than 1000 contacts at once.') order = request.args.get('order') if order == 'rank': results = contacts.search_util.search(g.db_session, g.namespace.account_id, filter, limit, offset) else: results = g.db_session.query(Contact). \ filter(Contact.account_id == g.namespace.account_id, Contact.source == 'local'). \ order_by(asc(Contact.id)).limit(limit).offset(offset).all() return jsonify(results)
def draft_update_api(public_id): parent_draft = g.db_session.query(Message). \ filter(Message.public_id == public_id).first() if parent_draft is None or not parent_draft.is_draft or \ parent_draft.namespace.id != g.namespace.id: return err(404, 'No draft with public id {}'.format(public_id)) if not parent_draft.is_latest: return err(409, 'Draft {} has already been updated to {}'.format( public_id, g.encoder.cereal(parent_draft.most_recent_revision))) # TODO(emfree): what if you try to update a draft on a *thread* that's been # deleted? data = request.get_json(force=True) to = data.get('to') cc = data.get('cc') bcc = data.get('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('files'), g.namespace.id, g.db_session) except InputError as e: return err(404, e.message) draft = sendmail.update_draft(g.db_session, g.namespace.account, parent_draft, to, subject, body, files, cc, bcc, tags) 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 webhooks_read_update_api(public_id): try: valid_public_id(public_id) except InputError: return err(400, 'Invalid webhook id {}'.format(public_id)) if request.method == 'GET': try: hook = g.db_session.query(Webhook).filter( Webhook.public_id == public_id, Webhook.namespace_id == g.namespace.id).one() return g.encoder.jsonify(hook) except NoResultFound: return err(404, "Couldn't find webhook with id {}" .format(public_id)) if request.method == 'PUT': data = request.get_json(force=True) # We only support updates to the 'active' flag. if data.keys() != ['active']: return err(400, 'Malformed webhook request') try: if data['active']: get_webhook_client().start_hook(public_id) else: get_webhook_client().stop_hook(public_id) return g.encoder.jsonify({"success": True}) except zerorpc.RemoteError: return err(404, "Couldn't find webhook with id {}" .format(public_id))
def event_update_api(public_id): try: valid_public_id(public_id) except InputError: return err(400, "Invalid event id {}".format(public_id)) data = request.get_json(force=True) try: valid_event_update(data) except InputError as e: return err(404, e.message) # Convert the data into our types where necessary # e.g. timestamps, participant_list if "start" in data: data["start"] = datetime.utcfromtimestamp(int(data.get("start"))) if "end" in data: data["end"] = datetime.utcfromtimestamp(int(data.get("end"))) if "participants" in data: data["participant_list"] = [] for p in data["participants"]: if "status" not in p: p["status"] = "noreply" data["participant_list"].append(p) del data["participants"] try: result = events.crud.update(g.namespace, g.db_session, public_id, data) except InputError as e: return err(404, e.message) if result is None: return err(404, "Couldn't find event with id {0}".format(public_id)) return g.encoder.jsonify(result)
def event_update_api(public_id): data = request.get_json(force=True) try: valid_event_update(data) except InputError as e: return err(404, e.message) # Convert the data into our types where necessary # e.g. timestamps, participant_list if 'start' in data: data['start'] = datetime.utcfromtimestamp(int(data.get('start'))) if 'end' in data: data['end'] = datetime.utcfromtimestamp(int(data.get('end'))) if 'participants' in data: data['participant_list'] = [] for p in data['participants']: if 'status' not in p: p['status'] = 'noreply' data['participant_list'].append(p) del data['participants'] try: result = events.crud.update(g.namespace, g.db_session, public_id, data) except InputError as e: return err(404, e.message) if result is None: return err(404, "Couldn't find event with id {0}". format(public_id)) return g.encoder.jsonify(result)
def message_api(public_id): try: valid_public_id(public_id) message = g.db_session.query(Message).filter( Message.public_id == public_id).one() assert int(message.namespace.id) == int(g.namespace.id) except InputError: return err(400, 'Invalid message id {}'.format(public_id)) except NoResultFound: return err(404, "Couldn't find message with id {0} " "on namespace {1}".format(public_id, g.namespace_public_id)) if request.method == 'GET': return g.encoder.jsonify(message) elif request.method == 'PUT': data = request.get_json(force=True) if data.keys() != ['unread'] or not isinstance(data['unread'], bool): return err(400, 'Can only change the unread attribute of a message') # TODO(emfree): Shouldn't allow this on messages that are actually # drafts. unread_tag = message.namespace.tags['unread'] unseen_tag = message.namespace.tags['unseen'] if data['unread']: message.is_read = False message.thread.apply_tag(unread_tag) else: message.is_read = True message.thread.remove_tag(unseen_tag) if all(m.is_read for m in message.thread.messages): message.thread.remove_tag(unread_tag) return g.encoder.jsonify(message)
def draft_get_api(public_id): try: valid_public_id(public_id) except InputError: return err(400, 'Invalid draft id {}'.format(public_id)) draft = sendmail.get_draft(g.db_session, g.namespace.account, public_id) if draft is None: return err(404, 'No draft found with id {}'.format(public_id)) return g.encoder.jsonify(draft)
def do(path=[]): if len(path) != 3: return err.err("Error") db = DB.DB() table = db.key2table(path[2]) if table == None: return err.err("Error Data is not exist.\n" + path[2]) res = {"status": "success", "result": db.select(table)} return json.dumps(res)
def tag_create_api(): data = request.get_json(force=True) if data.keys() != ['name']: return err(400, 'Malformed tag request') tag_name = data['name'] if not UserTag.name_available(tag_name, g.namespace.id, g.db_session): return err(409, 'Tag name not available') tag = UserTag(name=tag_name, namespace=g.namespace) g.db_session.commit() return jsonify(tag)
def contact_read_api(public_id): try: valid_public_id(public_id) except InputError: return err(400, "Invalid contact id {}".format(public_id)) # TODO auth with account object # Get all data for an existing contact. result = contacts.crud.read(g.namespace, g.db_session, public_id) if result is None: return err(404, "Couldn't find contact with id {0}".format(public_id)) return g.encoder.jsonify(result)
def contact_read_api(public_id): try: valid_public_id(public_id) except InputError: return err(400, 'Invalid contact id {}'.format(public_id)) # TODO auth with account object # Get all data for an existing contact. result = contacts.crud.read(g.namespace, g.db_session, public_id) if result is None: return err(404, "Couldn't find contact with id {0}".format(public_id)) return g.encoder.jsonify(result)
def file_read_api(public_id): try: valid_public_id(public_id) f = g.db_session.query(Block).filter( Block.public_id == public_id).one() return g.encoder.jsonify(f) except InputError: return err(400, 'Invalid file id {}'.format(public_id)) except NoResultFound: return err(404, "Couldn't find file with id {0} " "on namespace {1}".format(public_id, g.namespace_public_id))
def calendar_read_api(public_id): """Get all data for an existing calendar.""" try: valid_public_id(public_id) except InputError: return err(400, 'Invalid calendar id {}'.format(public_id)) result = events.crud.read_calendar(g.namespace, g.db_session, public_id) if result is None: return err(404, "Couldn't find calendar with id {0}".format(public_id)) return g.encoder.jsonify(result)
def tag_read_api(public_id): try: valid_public_id(public_id) tag = g.db_session.query(Tag).filter( Tag.public_id == public_id, Tag.namespace_id == g.namespace.id).one() except InputError: return err(400, '{} is not a valid id'.format(public_id)) except NoResultFound: return err(404, 'No tag found') return g.encoder.jsonify(tag)
def thread_api(public_id): try: valid_public_id(public_id) thread = g.db_session.query(Thread).filter( Thread.public_id == public_id, Thread.namespace_id == g.namespace.id).one() return g.encoder.jsonify(thread) except InputError: return err(400, 'Invalid thread id {}'.format(public_id)) except NoResultFound: return err(404, "Couldn't find thread with id `{0}` " "on namespace {1}".format(public_id, g.namespace_public_id))
def file_read_api(public_id): try: valid_public_id(public_id) f = g.db_session.query(Block).filter( Block.public_id == public_id, Block.namespace_id == g.namespace.id).one() return g.encoder.jsonify(f) except InputError: return err(400, 'Invalid file id {}'.format(public_id)) except NoResultFound: return err(404, "Couldn't find file with id {0} " "on namespace {1}".format(public_id, g.namespace_public_id))
def calendar_delete_api(public_id): try: calendar = get_calendar(public_id, g.namespace, g.db_session) except InputError as e: return err(404, e.message) if calendar.read_only: return err(400, "Cannot delete a read_only calendar.") result = events.crud.delete_calendar(g.namespace, g.db_session, public_id) return g.encoder.jsonify(result)
def calendar_read_api(public_id): """Get all data for an existing calendar.""" try: valid_public_id(public_id) except InputError: return err(400, 'Invalid calendar id {}'.format(public_id)) result = events.crud.read_calendar(g.namespace, g.db_session, public_id) if result is None: return err(404, "Couldn't find calendar with id {0}". format(public_id)) return g.encoder.jsonify(result)
def tag_create_api(): data = request.get_json(force=True) if data.keys() != ['name']: return err(400, 'Malformed tag request') tag_name = data['name'] if not Tag.name_available(tag_name, g.namespace.id, g.db_session): return err(409, 'Tag name not available') if len(tag_name) > MAX_INDEXABLE_LENGTH: return err(400, 'Tag name is too long.') tag = Tag(name=tag_name, namespace=g.namespace, user_created=True) g.db_session.commit() return g.encoder.jsonify(tag)
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 = data.get("to") cc = data.get("cc") bcc = data.get("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("files"), g.namespace.id, g.db_session) replyto_thread = get_thread(data.get("reply_to_thread"), 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 event_create_api(): # Handle ical uploads if request.headers.get('content-type') == 'text/calendar': ics_str = request.data new_events = events.crud.create_from_ics(g.namespace, g.db_session, ics_str) if not new_events: return err(400, "Couldn't parse .ics file.") return g.encoder.jsonify(new_events) data = request.get_json(force=True) try: calendar = get_calendar(data.get('calendar_id'), g.namespace, g.db_session) except InputError as e: return err(404, str(e)) if calendar.read_only: return err(400, "Can't create events on read_only calendar.") try: valid_event(data) except InputError as e: return err(404, str(e)) title = data.get('title', '') description = data.get('description') location = data.get('location') reminders = data.get('reminders') recurrence = data.get('recurrence') when = data.get('when') participants = data.get('participants', []) for p in participants: if 'status' not in p: p['status'] = 'noreply' new_event = events.crud.create(g.namespace, g.db_session, calendar, title, description, location, reminders, recurrence, when, participants) schedule_action('create_event', new_event, g.namespace.id, g.db_session) return g.encoder.jsonify(new_event)
def event_create_api(): # Handle ical uploads if request.headers['content-type'] == 'text/calendar': ics_str = request.data new_events = events.crud.create_from_ics(g.namespace, g.db_session, ics_str) if not new_events: return err(400, "Couldn't parse .ics file.") return g.encoder.jsonify(new_events) data = request.get_json(force=True) try: calendar = get_calendar(data.get('calendar_id'), g.namespace, g.db_session) except InputError as e: return err(404, e.message) if calendar.read_only: return err(400, "Can't create events on read_only calendar.") try: valid_event(data) except InputError as e: return err(404, e.message) title = data.get('title', '') description = data.get('description') location = data.get('location') reminders = data.get('reminders') recurrence = data.get('recurrence') when = data.get('when') participants = data.get('participants', []) for p in participants: if 'status' not in p: p['status'] = 'noreply' new_event = events.crud.create(g.namespace, g.db_session, calendar, title, description, location, reminders, recurrence, when, participants) schedule_action('create_event', new_event, g.namespace.id, g.db_session) return g.encoder.jsonify(new_event)
def file_read_api(public_id): try: valid_public_id(public_id) f = g.db_session.query(Block).filter(Block.public_id == public_id).one() if hasattr(f, "message"): assert int(f.message.namespace.id) == int(g.namespace.id) g.log.info("block's message namespace matches api context namespace") else: # Block was likely uploaded via file API and not yet sent in a msg g.log.debug("This block doesn't have a corresponding message: {}".format(f.public_id)) return g.encoder.jsonify(f) except InputError: return err(400, "Invalid file id {}".format(public_id)) except NoResultFound: return err(404, "Couldn't find file with id {0} " "on namespace {1}".format(public_id, g.namespace_public_id))
def stream_changes(): g.parser.add_argument('timeout', type=float, location='args') g.parser.add_argument('cursor', type=valid_public_id, location='args', required=True) args = strict_parse_args(g.parser, request.args) timeout = args['timeout'] or 3600 transaction_pointer = None cursor = args['cursor'] if cursor == '0': transaction_pointer = 0 else: query_result = g.db_session.query(Transaction.id).filter( Transaction.namespace_id == g.namespace.id, Transaction.public_id == cursor).first() if query_result is None: return err(400, 'Invalid cursor {}'.format(args['cursor'])) transaction_pointer = query_result[0] # Hack to not keep a database session open for the entire (long) request # duration. g.db_session.close() generator = delta_sync.streaming_change_generator( g.namespace.id, transaction_pointer=transaction_pointer, poll_interval=1, timeout=timeout) return Response(generator, mimetype='text/event-stream')
def sync_deltas(): g.parser.add_argument('cursor', type=valid_public_id, location='args', required=True) args = strict_parse_args(g.parser, request.args) cursor = args['cursor'] if cursor == '0': start_pointer = 0 else: try: start_pointer, = g.db_session.query(Transaction.id). \ filter(Transaction.public_id == cursor, Transaction.namespace_id == g.namespace.id).one() except NoResultFound: return err(404, 'Invalid cursor parameter') deltas, _ = delta_sync.format_transactions_after_pointer( g.namespace.id, start_pointer, g.db_session, args['limit']) response = { 'cursor_start': cursor, 'deltas': deltas, } if deltas: response['cursor_end'] = deltas[-1]['cursor'] else: # No changes. response['cursor_end'] = cursor return g.encoder.jsonify(response)
def calendar_update_api(public_id): try: calendar = get_calendar(public_id, g.namespace, g.db_session) except InputError as e: return err(404, e.message) if calendar.read_only: return err(400, "Cannot update a read_only calendar.") data = request.get_json(force=True) result = events.crud.update_calendar(g.namespace, g.db_session, public_id, data) if result is None: return err(404, "Couldn't find calendar with id {0}".format(public_id)) return g.encoder.jsonify(result)
def contact_read_api(public_id): # TODO auth with account object # Get all data for an existing contact. result = contacts.crud.read(g.namespace, g.db_session, public_id) if result is None: return err(404, "Couldn't find contact with id {0}".format(public_id)) return jsonify(result)
def webhooks_create_api(): try: parameters = request.get_json(force=True) result = get_webhook_client().register_hook(g.namespace.id, parameters) return Response(result, mimetype='application/json') except zerorpc.RemoteError: return err(400, 'Malformed webhook request')
def sync_deltas(): g.parser.add_argument('cursor', type=valid_public_id, location='args', required=True) g.parser.add_argument('exclude_types', type=valid_delta_object_types, location='args') args = strict_parse_args(g.parser, request.args) cursor = args['cursor'] if cursor == '0': start_pointer = 0 else: try: start_pointer, = g.db_session.query(Transaction.id). \ filter(Transaction.public_id == cursor, Transaction.namespace_id == g.namespace.id).one() except NoResultFound: return err(404, 'Invalid cursor parameter') exclude_types = args.get('exclude_types') deltas, _ = delta_sync.format_transactions_after_pointer( g.namespace.id, start_pointer, g.db_session, args['limit'], delta_sync._format_transaction_for_delta_sync, exclude_types) response = { 'cursor_start': cursor, 'deltas': deltas, } if deltas: response['cursor_end'] = deltas[-1]['cursor'] else: # No changes. response['cursor_end'] = cursor return g.encoder.jsonify(response)
def stream_changes(): g.parser.add_argument('timeout', type=float, location='args') g.parser.add_argument('cursor', type=valid_public_id, location='args', required=True) g.parser.add_argument('exclude_types', type=valid_delta_object_types, location='args') args = strict_parse_args(g.parser, request.args) timeout = args['timeout'] or 3600 transaction_pointer = None cursor = args['cursor'] if cursor == '0': transaction_pointer = 0 else: query_result = g.db_session.query(Transaction.id).filter( Transaction.namespace_id == g.namespace.id, Transaction.public_id == cursor).first() if query_result is None: return err(400, 'Invalid cursor {}'.format(args['cursor'])) transaction_pointer = query_result[0] exclude_types = args.get('exclude_types') # Hack to not keep a database session open for the entire (long) request # duration. g.db_session.close() generator = delta_sync.streaming_change_generator( g.namespace.id, transaction_pointer=transaction_pointer, poll_interval=1, timeout=timeout, exclude_types=exclude_types) return Response(generator, mimetype='text/event-stream')
def event_create_api(): # Handle ical uploads if request.headers["content-type"] == "text/calendar": ics_str = request.data new_events = events.crud.create_from_ics(g.namespace, g.db_session, ics_str) if not new_events: return err(400, "Couldn't parse .ics file.") return g.encoder.jsonify(new_events) data = request.get_json(force=True) try: valid_event(data) except InputError as e: return err(404, e.message) start = datetime.utcfromtimestamp(int(data.get("start"))) end = datetime.utcfromtimestamp(int(data.get("end"))) subject = data.get("subject", "") body = data.get("body") location = data.get("location") reminders = data.get("reminders") recurrence = data.get("recurrence") busy = int(data.get("busy")) all_day = int(data.get("all_day")) participants = data.get("participants", []) for p in participants: if "status" not in p: p["status"] = "noreply" new_contact = events.crud.create( g.namespace, g.db_session, subject, body, location, reminders, recurrence, start, end, busy, all_day, participants, ) return g.encoder.jsonify(new_contact)
def sync_deltas(): g.parser.add_argument("cursor", type=valid_public_id, location="args", required=True) args = strict_parse_args(g.parser, request.args) try: results = delta_sync.get_entries_from_public_id(g.namespace.id, args["cursor"], g.db_session, args["limit"]) return g.encoder.jsonify(results) except ValueError: return err(404, "Invalid cursor parameter")
def generate_cursor(): data = request.get_json(force=True) if data.keys() != ["start"] or not isinstance(data["start"], int): return err(400, "generate_cursor request body must have the format " '{"start": <Unix timestamp>}') timestamp = int(data["start"]) cursor = delta_sync.get_public_id_from_ts(g.namespace.id, timestamp, g.db_session) return g.encoder.jsonify({"cursor": cursor})
def event_read_api(public_id): # TODO auth with account object # Get all data for an existing event. result = events.crud.read(g.namespace, g.db_session, public_id) if result is None: return err(404, "Couldn't find event with id {0}". format(public_id)) return g.encoder.jsonify(result)
def thread_api_update(public_id): try: valid_public_id(public_id) thread = g.db_session.query(Thread).filter( Thread.public_id == public_id, Thread.namespace_id == g.namespace.id).one() except InputError: return err(400, 'Invalid thread id {}'.format(public_id)) except NoResultFound: return err( 404, "Couldn't find thread with id `{0}` " "on namespace {1}".format(public_id, g.namespace_public_id)) data = request.get_json(force=True) if not set(data).issubset({'add_tags', 'remove_tags'}): return err(400, 'Can only add or remove tags from thread.') removals = data.get('remove_tags', []) for tag_identifier in removals: tag = g.db_session.query(Tag).filter( Tag.namespace_id == g.namespace.id, or_(Tag.public_id == tag_identifier, Tag.name == tag_identifier)).first() if tag is None: return err(404, 'No tag found with name {}'.format(tag_identifier)) if not tag.user_removable: return err(400, 'Cannot remove tag {}'.format(tag_identifier)) try: thread.remove_tag(tag, execute_action=True) except ActionError as e: return err(e.error, str(e)) additions = data.get('add_tags', []) for tag_identifier in additions: tag = g.db_session.query(Tag).filter( Tag.namespace_id == g.namespace.id, or_(Tag.public_id == tag_identifier, Tag.name == tag_identifier)).first() if tag is None: return err(404, 'No tag found with name {}'.format(tag_identifier)) if not tag.user_addable: return err(400, 'Cannot add tag {}'.format(tag_identifier)) try: thread.apply_tag(tag, execute_action=True) except ActionError as e: return err(e.error, str(e)) g.db_session.commit() return g.encoder.jsonify(thread)
def sync_events(): start_stamp = request.args.get('stamp') try: limit = int(request.args.get('limit', 100)) except ValueError: return err(400, 'Invalid limit parameter') if limit <= 0: return err(400, 'Invalid limit parameter') if start_stamp is None: return err(400, 'No stamp parameter in sync request.') try: results = client_sync.get_entries_from_public_id( g.namespace.id, start_stamp, g.db_session, limit) return g.encoder.jsonify(results) except ValueError: return err(404, 'Invalid stamp parameter')
def start(): g.db_session = InboxSession(engine) g.log = current_app.logger try: g.namespace = g.db_session.query(Namespace) \ .filter(Namespace.public_id == g.namespace_public_id).one() g.encoder = APIEncoder(g.namespace.public_id) except NoResultFound: return err(404, "Couldn't find namespace with id `{0}` ".format( g.namespace_public_id)) try: g.limit = int(request.args.get('limit', 10)) g.offset = int(request.args.get('offset', 0)) except ValueError: return err(400, 'limit and offset parameters must be integers') if g.limit < 0 or g.offset < 0: return err(400, 'limit and offset parameters must be nonnegative ' 'integers') if g.limit > MAX_LIMIT: return err(400, 'cannot request more than {} resources at once.'. format(MAX_LIMIT)) try: g.api_filter = Filter( namespace_id=g.namespace.id, subject=request.args.get('subject'), thread_public_id=request.args.get('thread'), to_addr=request.args.get('to'), from_addr=request.args.get('from'), cc_addr=request.args.get('cc'), bcc_addr=request.args.get('bcc'), any_email=request.args.get('any_email'), started_before=request.args.get('started_before'), started_after=request.args.get('started_after'), last_message_before=request.args.get('last_message_before'), last_message_after=request.args.get('last_message_after'), filename=request.args.get('filename'), tag=request.args.get('tag'), limit=g.limit, offset=g.offset, order_by=request.args.get('order_by'), db_session=g.db_session) except ValueError as e: return err(400, e.message)
def event_read_api(public_id): """Get all data for an existing event.""" try: valid_public_id(public_id) except InputError: return err(400, 'Invalid event id {}'.format(public_id)) g.parser.add_argument('participant_id', type=valid_public_id, location='args') g.parser.add_argument('action', type=valid_event_action, location='args') g.parser.add_argument('rsvp', type=valid_rsvp, location='args') args = strict_parse_args(g.parser, request.args) if 'action' in args: # Participants are able to RSVP to events by clicking on links (e.g. # that are emailed to them). Therefore, the RSVP action is invoked via # a GET. if args['action'] == 'rsvp': try: participant_id = args.get('participant_id') if not participant_id: return err(404, "Must specify a participant_id with rsvp") participant = g.db_session.query(Participant).filter_by( public_id=participant_id).one() participant.status = args['rsvp'] g.db_session.commit() result = events.crud.read(g.namespace, g.db_session, public_id) if result is None: return err( 404, "Couldn't find event with id {0}".format(public_id)) return g.encoder.jsonify(result) except NoResultFound: return err( 404, "Couldn't find participant with id `{0}` ".format( participant_id)) result = events.crud.read(g.namespace, g.db_session, public_id) if result is None: return err(404, "Couldn't find event with id {0}".format(public_id)) return g.encoder.jsonify(result)
def calendar_update_api(public_id): try: calendar = get_calendar(public_id, g.namespace, g.db_session) except InputError as e: return err(404, e.message) if calendar.read_only: return err(400, "Cannot update a read_only calendar.") data = request.get_json(force=True) result = events.crud.update_calendar(g.namespace, g.db_session, public_id, data) if result is None: return err(404, "Couldn't find calendar with id {0}". format(public_id)) return g.encoder.jsonify(result)
def draft_delete_api(public_id): data = request.get_json(force=True) if data.get('version') is None: return err(400, 'Must specify version to delete') version = data.get('version') try: valid_public_id(public_id) draft = g.db_session.query(Message).filter( Message.public_id == public_id).one() except InputError: return err(400, 'Invalid public id {}'.format(public_id)) except NoResultFound: return err(404, 'No draft found with public_id {}'.format(public_id)) if draft.namespace != g.namespace: return err(404, 'No draft found with public_id {}'.format(public_id)) if not draft.is_draft: return err( 400, 'Message with public id {} is not a draft'.format(public_id)) if draft.version != version: return err( 409, 'Draft {0}.{1} has already been updated to version ' '{2}'.format(public_id, version, draft.version)) try: result = sendmail.delete_draft(g.db_session, g.namespace.account, public_id) except ActionError as e: return err(e.error, str(e)) return g.encoder.jsonify(result)
def file_download_api(public_id): try: valid_public_id(public_id) f = g.db_session.query(Block).filter( Block.public_id == public_id, Block.namespace_id == g.namespace.id).one() except InputError: return err(400, 'Invalid file id {}'.format(public_id)) except NoResultFound: return err( 404, "Couldn't find file with id {0} " "on namespace {1}".format(public_id, g.namespace_public_id)) # Here we figure out the filename.extension given the # properties which were set on the original attachment # TODO consider using werkzeug.secure_filename to sanitize? if f.content_type: ct = f.content_type.lower() else: # TODO Detect the content-type using the magic library # and set ct = the content type, which is used below g.log.error("Content type not set! Defaulting to text/plain") ct = 'text/plain' if f.filename: name = f.filename else: g.log.debug("No filename. Generating...") if ct in common_extensions: name = 'attachment.{0}'.format(common_extensions[ct]) else: g.log.error("Unknown extension for content-type: {0}".format(ct)) # HACK just append the major part of the content type name = 'attachment.{0}'.format(ct.split('/')[0]) # TODO the part.data object should really behave like a stream we can read # & write to response = make_response(f.data) response.headers['Content-Type'] = 'application/octet-stream' # ct response.headers[ 'Content-Disposition'] = "attachment; filename={0}".format(name) g.log.info(response.headers) return response
def contact_create_api(): # TODO(emfree) Detect attempts at duplicate insertions. data = request.get_json(force=True) name = data.get('name') email = data.get('email') if not any((name, email)): return err(400, 'Contact name and email cannot both be null.') new_contact = contacts.crud.create(g.namespace, g.db_session, name, email) return g.encoder.jsonify(new_contact)