def handle_login(): """ Login endpoint. It is used for logging a user in. If the email and the password passed in the request body are valid ones it returns a valid token for the authentication in the API gateway. """ data = request.get_json() if validate_request_paylaod(data): email = data['email'] password = data['password'] user_id = get_user_id_by_credentials(email, password) if user_id: # retrieves a new token for this user KONG_JWT_API = f"http://{KONG_IP}:8001/consumers/{email}/jwt" response = requests.post(KONG_JWT_API, data={"algorithm": "HS256"}) data = response.json() headers = {"typ": "JWT", "alg": "HS256"} claims = {"iss": data['key']} token = jwt.encode(claims, data['secret'], headers=headers) if not response.status_code == 201: logger.error(f'Could not build token for {request}') return ko(dict(error='Could not build token')) logger.info(f"Generated JWT Token: {token} for user {user_id}") return ok(jsonify(dict(auth_token=token.decode(), id=user_id))) return bad_request(jsonify(dict(error='invalid_data')))
def handle_get_event(user_id, calendar_id, id): """ This query endpoint returns an event resource. :return: a json object with the event info on a 200 response status code. """ cal_id = get_user_calendar_id(user_id, calendar_id) if not cal_id: return not_found(jsonify(dict(error='Calendar not found'))) event, event_recurrences = get_event(user_id, calendar_id, id) if not event: return not_found(jsonify(dict(error='Event not found'))) response = dict(name=event.name, location=event.location, start_time=strftime(event.start_time), end_time=strftime(event.end_time), next_is_base=event.next_is_base, recurrence_rule=event.recurrence_rule.value, until=strftime(event.until), flex=event.flex, flex_duration=event.flex_duration, recurrences=[]) for recurrence in event_recurrences: response['recurrences'].append( dict(recurrence_id=recurrence.id, start_time=strftime(recurrence.start_time), end_time=strftime(recurrence.end_time))) return ok(jsonify(response))
def handle_get_global_preferences(user_id): """ This query endpoint returns the global preferences of a user. :return: a json object with the global preferences on a 200 response status code. """ global_preferences = get_user_global_preferences(user_id) if global_preferences: response = dict(global_preferences.preferences) return ok(jsonify(response)) return not_found(jsonify(dict(error='User not found')))
def handle_get(user_id): """ This is the only endpoint query side. It is used for retrieving a user resource data. :return: a json object with the user info on a 200 response status code. """ user = get_user_by_id(user_id) if user: return ok( jsonify( dict(id=user.id, email=user.email, first_name=user.first_name, last_name=user.last_name))) return not_found(jsonify(dict(error='User not found')))
def handle_delete(user_id, id): """ Delete calendar endpoint. It deletes a calendar. """ aggregate_status = build_calendar_aggregate() if aggregate_status == -1: logger.error( f'Could not build calendar aggregate for request: {request}') return ko(jsonify(dict(error='cannot build calendar aggregate'))) if not (str(user_id) in aggregate_status.keys()): return not_found(jsonify(dict(error='invalid user'))) if not (str(id) in aggregate_status[str(user_id)]['calendars'].keys()): return not_found(jsonify(dict(error='invalid calendar'))) event = CalendarDeletedEvent(user_id=user_id, id=id) session = EventStoreSession() try: # both of these are wrapped inside a unique try catch because this must be an atomic operation. CalendarEvent.append_event(event) event_published = publish_event(get_sending_channel(), event, CALENDAR_DELETED) if not event_published: logger.error( f'Could not publish event on event bus for request: {request}') session.rollback() return ko( jsonify(dict(error='could not publish event on event bus'))) else: session.commit() logger.info(f'Deleted calendar for user {user_id} with id: {id}') return ok(jsonify(dict(user_id=user_id, id=id))) except SQLAlchemyError: session.rollback() logger.error(f'Could not append to event store for request: {request}') return ko(jsonify(dict(error='could not append to event store'))) finally: session.close()
def handle_preferences(user_id): """ Global preferences modify endpoint. It modifies user global preferences. """ aggregate_status = build_calendar_aggregate() if aggregate_status == -1: logger.error(f'Could not build user aggregate for request: {request}') return ko(jsonify(dict(error='cannot build user aggregate'))) if not (str(user_id) in aggregate_status.keys()): return not_found(jsonify(dict(error='invalid user'))) data = request.get_json() if validate_global_preferences(data): # checks the request payload is correctly formed event = GlobalPreferencesModifiedEvent(user_id=user_id, preferences=data) session = EventStoreSession() try: # both of these are wrapped inside a unique try catch because this must be an atomic operation. CalendarEvent.append_event(event) event_published = publish_event(get_sending_channel(), event, PREFERENCES_MODIFIED) if not event_published: logger.error(f'Could not publish event on event bus for request: {request}') session.rollback() return ko(jsonify(dict(error='could not publish event on event bus'))) else: session.commit() logger.info(f'Modified global preferences of user with id: {user_id}') return ok(jsonify(dict(id=user_id))) except SQLAlchemyError: session.rollback() logger.error(f'Could not append to event store for request: {request}') return ko(jsonify(dict(error='could not append to event store'))) finally: session.close() return bad_request(jsonify(dict(error='invalid data')))
def handle_get_schedule(user_id): """ This query endpoint returns the user schedule. :return: a json object with the list of all the recurrences in the user schedule on a 200 response status code. """ recurrences_and_names = get_schedule(user_id) response = dict(recurrences=[]) for recurrence, name in recurrences_and_names: response['recurrences'].append( dict(user_id=user_id, calendar_id=recurrence.calendar_id, event_id=recurrence.event_id, id=recurrence.id, start_time=strftime(recurrence.start_time), end_time=strftime(recurrence.end_time), event_name=name)) return ok(jsonify(response))
def handle_delete(user_id): """ Delete user endpoint. It deletes a user resource. """ aggregate_status = build_user_aggregate() if aggregate_status == -1: logger.error(f'Could not build user aggregate for request: {request}') return ko(jsonify(dict(error='cannot build user aggregate'))) if not (str(user_id) in aggregate_status.keys()): return not_found(jsonify(dict(error='invalid user'))) event = UserDeletedEvent(id=user_id) session = EventStoreSession() try: UserEvent.append_event(event) event_published = publish_event(get_sending_channel(), event, USER_DELETED) if not event_published: logger.error( f'Could not publish event on event bus for request: {request}') session.rollback() return ko( jsonify(dict(error='could not publish event on event bus'))) else: session.commit() logger.info(f'Deleted user with id: {user_id}') return ok(jsonify(dict(id=user_id))) except SQLAlchemyError: session.rollback() logger.error(f'Could not append to event store for request: {request}') return ko(jsonify(dict(error='could not append to event store'))) finally: session.close()
def handle_get_all_calendar_events(user_id, calendar_id): """ This query endpoint returns all the events of a user calendar. :return: a json object listing all the events in the user calendar on a 200 response status code. """ cal_id = get_user_calendar_id(user_id, calendar_id) if not cal_id: return not_found(jsonify(dict(error='Calendar not found'))) events = get_all_calendar_events(user_id, calendar_id) response = {} for event, event_recurrences in events: response[str(event.id)] = dict( name=event.name, location=event.location, start_time=strftime(event.start_time), end_time=strftime(event.end_time), next_is_base=event.next_is_base, recurrence_rule=event.recurrence_rule.value, until=strftime(event.until), flex=event.flex, flex_duration=event.flex_duration, recurrences=dict() ) for recurrence in event_recurrences: response[str(event.id)]['recurrences'].update( { str(recurrence.id): dict( start_time=strftime(recurrence.start_time), end_time=strftime(recurrence.end_time) ) } ) return ok(jsonify(response))
def handle_get_all_calendars(user_id): """ This query endpoint returns all the calendars of a given user. :return: a json object with a list of all the calendars on a 200 response status code. """ if not (get_user_global_preferences(user_id)): return not_found(jsonify(dict(error='User not found'))) calendars = get_all_user_calendars(user_id) response = dict(calendars=[]) for calendar in calendars: response['calendars'].append( dict(id=calendar.id, name=calendar.name, description=calendar.description, base=calendar.base, color=calendar.color, active=calendar.active, carbon=calendar.carbon, preferences=calendar.preferences)) return ok(jsonify(response))
def handle_get_calendar(user_id, id): """ This query endpoint return a calendar of a given user. :return: a json object with the calendar info on a 200 response status code. """ if not (get_user_global_preferences(user_id)): return not_found(jsonify(dict(error='User not found'))) calendar = get_calendar(user_id, id) if calendar: response = dict(name=calendar.name, description=calendar.description, base=calendar.base, color=calendar.color, active=calendar.active, carbon=calendar.carbon, preferences=calendar.preferences) return ok(jsonify(response)) return not_found(jsonify(dict(error='Calendar not found')))
def handle_modify(user_id, id): """ Calendar modify endpoint. It modifies a calendar resource. """ data = request.get_json() aggregate_status = build_calendar_aggregate() if aggregate_status == -1: logger.error( f'Could not build calendar aggregate for request: {request}') return ko(jsonify(dict(error='cannot build calendar aggregate'))) if not (str(user_id) in aggregate_status.keys()): return not_found(jsonify(dict(error='invalid user'))) if not (str(id) in aggregate_status[str(user_id)]['calendars'].keys()): return not_found(jsonify(dict(error='invalid calendar'))) user_global_preferences = aggregate_status[str(user_id)]['preferences'] # validate the request payload and check if the preferences are applicable given the global one # in the current aggregate status if validate_calendar_data(data) and validate_calendar_preferences( data['preferences'], user_global_preferences): user_calendars = aggregate_status[str(user_id)]['calendars'] user_calendars.pop(str(id)) # pop for preserving idempotency if data['name'] in ((value['name'] for value in user_calendars.values())): return bad_request(jsonify(dict(error='invalid data'))) data['user_id'] = user_id data['id'] = id event = CalendarModifiedEvent(**data) session = EventStoreSession() try: # both of these are wrapped inside a unique try catch because this must be an atomic operation. CalendarEvent.append_event(event) event_published = publish_event(get_sending_channel(), event, CALENDAR_MODIFIED) if not event_published: logger.error( f'Could not publish event on event bus for request: {request}' ) session.rollback() return ko( jsonify( dict(error='could not publish event on event bus'))) else: session.commit() logger.info( f'Modified calendar for user {user_id} with id: {id}') return ok(jsonify(dict(user_id=user_id, id=id))) except SQLAlchemyError: session.rollback() logger.error( f'Could not append to event store for request: {request}') return ko(jsonify(dict(error='could not append to event store'))) finally: session.close() return bad_request(jsonify(dict(error='invalid data')))
def handle_recurrence_delete(user_id, calendar_id, event_id, id): """ This endpoint deletes a recurrence of an event. """ aggregate_status = build_event_aggregate() if aggregate_status == -1: logger.error( f'Could not build calendar aggregate for request: {request}') return ko(jsonify(dict(error='cannot build calendar aggregate'))) if not (str(user_id) in aggregate_status.keys()): return not_found(jsonify(dict(error='invalid user'))) if not (str(calendar_id) in aggregate_status[str(user_id)]['calendars'].keys()): return not_found(jsonify(dict(error='invalid calendar'))) if not (str(event_id) in aggregate_status[str(user_id)]['calendars'][str( calendar_id)]['events'].keys()): return not_found(jsonify(dict(error='invalid event'))) if not (str(id) in aggregate_status[str(user_id)]['calendars'][str( calendar_id)]['events'][str(event_id)]['recurrences'].keys()): return not_found(jsonify(dict(error='invalid recurrence'))) data = dict() data['user_id'] = user_id data['calendar_id'] = calendar_id data['event_id'] = event_id data['id'] = id event = RecurrenceDeletedEvent(**data) session = EventStoreSession() try: # both of these are wrapped inside a unique try catch because this must be an atomic operation. EventEvent.append_event(event) event_published = publish_event(get_sending_channel(), event, RECURRENCE_DELETED) if not event_published: logger.error( f'Could not publish event on event bus for request: {request}') session.rollback() return ko( jsonify(dict(error='could not publish event on event bus'))) else: session.commit() logger.info( f'Deleted reccurece for user {user_id} on calendar id {calendar_id} on event id {event_id} with id {id}' ) return ok( jsonify(dict(user_id=user_id, calendar_id=calendar_id, id=id))) except SQLAlchemyError: session.rollback() logger.error(f'Could not append to event store for request: {request}') return ko(jsonify(dict(error='could not append to event store'))) finally: session.close()
def handle_modify(user_id, calendar_id, id): """ This endpoint modifies an event resource. """ data = request.get_json() aggregate_status = build_event_aggregate() if aggregate_status == -1: logger.error( f'Could not build calendar aggregate for request: {request}') return ko(jsonify(dict(error='cannot build calendar aggregate'))) if not (str(user_id) in aggregate_status.keys()): return not_found(jsonify(dict(error='invalid user'))) if not (str(calendar_id) in aggregate_status[str(user_id)]['calendars'].keys()): return not_found(jsonify(dict(error='invalid calendar'))) if not (str(id) in aggregate_status[str(user_id)]['calendars'][str( calendar_id)]['events'].keys()): return not_found(jsonify(dict(error='invalid event'))) aggregate_status[str(user_id)]['calendars'][str( calendar_id)]['events'].pop(str(id)) user_schedule = aggregate_status[str(user_id)]['calendars'] if validate_event(data) and event_can_be_inserted(data, user_schedule): if 'until' not in data: data['until'] = None if 'flex' not in data: data['flex'] = None data['flex_duration'] = None data['user_id'] = user_id data['calendar_id'] = calendar_id data['id'] = id event = EventModifiedEvent(**data) session = EventStoreSession() try: # both of these are wrapped inside a unique try catch because this must be an atomic operation. EventEvent.append_event(event) event_published = publish_event(get_sending_channel(), event, EVENT_MODIFIED) if not event_published: logger.error( f'Could not publish event on event bus for request: {request}' ) session.rollback() return ko( jsonify( dict(error='could not publish event on event bus'))) else: session.commit() logger.info( f'Modified event for user {user_id} on calendar id {calendar_id} with id {id}' ) return ok( jsonify( dict(user_id=user_id, calendar_id=calendar_id, id=id))) except SQLAlchemyError: session.rollback() logger.error( f'Could not append to event store for request: {request}') return ko(jsonify(dict(error='could not append to event store'))) finally: session.close() return bad_request(jsonify(dict(error='invalid data')))