Example #1
0
def get_user_likes(event_body, context):
    f"""
    Handles retrieval of list of photos a user has liked.
    :param event: (Auto) JSON-encoded API Gateway pass-through.
    :param {API_USER_EMAIL}: (Req) User identifier
    :param {API_USER_SESSION_KEY}: (Req) Temporary user-specific session key for authentication
    :param context: (Auto) JSON-encoded API Gateway pass-through. Not used.
    :return: Array of photo ids that the user has liked if successful, under "user_likes" key
    """

    # get user information
    user_info = USER_TABLE.get_item(
        Key={API_USER_EMAIL: event_body[API_USER_EMAIL]})

    try:
        return APIResponseSuccess(
            response_content={
                "user_likes": user_info['Item'][USER_PHOTO_LIKES_FIELD]
            }).send()

    except KeyError:
        return APIResponseSuccess(message="User has liked no photos",
                                  response_content={
                                      "user_likes": []
                                  }).send()
Example #2
0
def get_photo_info(event_body, context):
    f"""
    Handles request for all information about a particular photo.
    :param event: (Auto) JSON-encoded API Gateway pass-through.
    :param {API_USER_EMAIL}: (Req) User identifier
    :param {API_USER_SESSION_KEY}: (Req) Temporary user-specific session key for authentication
    :param {API_PHOTO_ID}: (Req) ID fo photo to fetch all information for
    :param context: (Auto) JSON-encoded API Gateway pass-through. Not used.
    :return: dictionary with photo information under "photo_info" key.
    """

    photo_info_raw = PHOTOS_TABLE.get_item(
        Key={PHOTO_ID_FIELD: event_body[API_PHOTO_ID]})

    try:
        photo_info_raw['Item']
    except KeyError:
        return APIResponse(status_code=404,
                           message="Photo doesn't exist for this ID").send()

    photo_info = dict()

    # convert data types from DynamDB to Python and JSON-friendly
    for k, v in photo_info_raw['Item'].items():
        if isinstance(v, set):
            photo_info[k] = list(v)
        elif isinstance(v, boto3.dynamodb.types.Decimal):
            photo_info[k] = int(v)
        else:
            photo_info[k] = v

    return APIResponseSuccess(response_content={
        "photo_info": photo_info
    }).send()
Example #3
0
def set_user_preferences(event_body, context):
    f"""
    Handles setting of additional user information / preferences.
    :param event: (Auto) JSON-encoded API Gateway pass-through.
    :param {API_USER_EMAIL}: (Req) User identifier
    :param {API_USER_SESSION_KEY}: (Req) Temporary user-specific session key for authentication
    :param {API_USER_AGE}: (Req) User age to be stored in DB (empty string if none)
    :param {API_USER_GENDER}: (Req) User gender to be stored in DB (M/F/O, empty string if none)
    :param {API_USER_SIZE}: (Req) User size ot be stored in DB (S/M/L/etc, empty string if none)
    :param {API_USER_NAME}: (Req) Name of user to be displayed (empty string if none)
    :param context: (Auto) JSON-encoded API Gateway pass-through. Not used.
    :return: User information if update successful
    """

    # update user age, size, gender, name
    USER_TABLE.update_item(
        Key={API_USER_EMAIL: event_body[API_USER_EMAIL]},
        UpdateExpression=
        f"""SET {USER_AGE_FIELD} = :user_age, {USER_SIZE_FIELD} = :user_size, {USER_GENDER_FIELD} = 
        :user_gender, {USER_NAME_FIELD} = :user_name""",
        ExpressionAttributeValues={
            ':user_age': event_body[API_USER_AGE],
            ':user_size': event_body[API_USER_SIZE],
            ':user_gender': event_body[API_USER_GENDER],
            ':user_name': event_body[API_USER_NAME]
        })

    # return success message, getting fresh user info from database for response
    return APIResponseSuccess(
        message="User preferences updated.",
        response_content={
            "user_info":
            json.loads(get_user_preferences(event_body,
                                            context)['body'])['user_info']
        }).send()
Example #4
0
def user_submit_petition(event_body, context):
    f"""
    Handles user petitioning for a location to add offers.
    :param event: (Auto) JSON-encoded API Gateway pass-through.
    :param {API_USER_EMAIL}: (Req) User identifier
    :param {API_USER_SESSION_KEY}: (Req) Temporary user-specific session key for authentication
    :param {API_LOCATION_ID}: (Req) Google Location ID for petition location
    :param context: (Auto) JSON-encoded API Gateway pass-through. Not used.
    :return: number of petitions under location ID key if successful
    """

    # get user's current petitions
    user_info = USER_TABLE.get_item(Key={USER_EMAIL_FIELD: event_body[API_USER_EMAIL]})

    try:
        user_petitioned_locations_list = user_info['Item'][USER_PETITIONED_LOCATIONS_FIELD]
    except KeyError:
        user_petitioned_locations_list = []

    # check user hasn't already petitioned location
    if event_body[API_LOCATION_ID] in user_petitioned_locations_list:
        return APIResponse(status_code=409, message="User has already petitioned this location.").send()

    # update user petitions
    user_petitioned_locations_list.append(event_body[API_LOCATION_ID])

    USER_TABLE.update_item(
        Key={USER_EMAIL_FIELD: event_body[API_USER_EMAIL]},
        UpdateExpression=f'SET {USER_PETITIONED_LOCATIONS_FIELD} = :user_petitioned_locations',
        ExpressionAttributeValues={
            ':user_petitioned_locations': user_petitioned_locations_list
        })

    postgres_db = psycopg2.connect(dbname=AWS_POSTGRES_DB, user=AWS_POSTGRES_USER, password=AWS_POSTGRES_PASSWORD,
                                   host=AWS_POSTGRES_URL)
    postgres_db.autocommit = True
    postgres_db_cursor = postgres_db.cursor(cursor_factory=RealDictCursor)

    # save petition to petitions table
    postgres_db_cursor.execute(f"select {PETITION_COUNT_FIELD} from {PETITION_TABLE_NAME} where "
                               f"{LOCATION_ID_FIELD} = %(loc_id)s", {'loc_id': event_body[API_LOCATION_ID]})
    r = postgres_db_cursor.fetchall()

    if len(r) == 0:
        new_petition_count = 1
        postgres_db_cursor.execute(f"insert into {PETITION_TABLE_NAME} values (%(loc_id)s, %(new_count)s);",
                                   {'new_count': new_petition_count, 'loc_id': event_body[API_LOCATION_ID]})
    else:
        new_petition_count = r[0][LOCATION_ID_FIELD] + 1
        postgres_db_cursor.execute(f"update {PETITION_TABLE_NAME} set {PETITION_COUNT_FIELD} = %(new_count)s where "
                                   f"{LOCATION_ID_FIELD} = %(loc_id)s;", {'new_count': new_petition_count,
                                                                          'loc_id': event_body[API_LOCATION_ID]})

    postgres_db_cursor.close()
    postgres_db.close()

    return APIResponseSuccess(response_content={event_body[API_LOCATION_ID]: str(new_petition_count)}).send()
Example #5
0
def user_session_leave(event_body, context):
    f"""
    Handles deletion of session key for a particular user if requested (manual ending of session).
    :param event: (Auto) JSON-encoded API Gateway pass-through.
    :param {API_USER_EMAIL}: (Req) User identifier
    :param {API_USER_SESSION_KEY}: (Req) Temporary user-specific session key for authentication
    :return: "API key destroyed" message if successful
    """

    if session_authoriser(event_body[API_USER_EMAIL],
                          event_body[API_USER_SESSION_KEY], context):
        USER_TABLE.update_item(
            Key={API_USER_EMAIL: event_body[API_USER_EMAIL]},
            UpdateExpression=f'REMOVE {USER_SESSION_KEYS_FIELD}')
        return APIResponseSuccess(message="API key destroyed").send()
    else:
        return APIResponseUnauthorized().send()
Example #6
0
def user_delete_photo(event_body, context):
    f"""
    Handles user deletion of photo, removing from S3, user and photos databases.
    :param event: (Auto) JSON-encoded API Gateway pass-through.
    :param {API_USER_EMAIL}: (Req) User identifier
    :param {API_USER_SESSION_KEY}: (Req) Temporary user-specific session key for authentication
    :param {API_PHOTO_ID}: (Req) ID of photo to be deleted
    :param context: (Auto) JSON-encoded API Gateway pass-through. Not used.
    :return: "Photo deleted" message if successful
    """

    # get list of current user photos -- run this first to check that user originally uploaded the photo to be deleted
    current_user_data = USER_TABLE.get_item(Key={API_USER_EMAIL: event_body[API_USER_EMAIL]})

    try:
        user_photos_list = current_user_data['Item'][USER_PHOTOS_FIELD]
        # remove image filename from list of user photos
        user_photos_list.remove(event_body[API_PHOTO_ID])
    except (KeyError, ValueError):
        return APIResponseForbidden(message="User has not posted this image.").send()

    USER_TABLE.update_item(
        Key={
            API_USER_EMAIL: event_body[API_USER_EMAIL]
        },
        UpdateExpression=f'SET {USER_PHOTOS_FIELD} = :user_photos_list',
        ExpressionAttributeValues={
            ':user_photos_list': user_photos_list
        })

    # remove image from photos table
    PHOTOS_TABLE.delete_item(
        Key={PHOTO_ID_FIELD: event_body[API_PHOTO_ID]}
    )

    # delete photo from storage
    r = S3_STORAGE.delete_object(
        Bucket=S3_IMAGE_UPLOAD_BUCKET,
        Key=event_body[API_PHOTO_ID])

    return APIResponseSuccess(message="Photo deleted").send()
Example #7
0
def get_user_preferences(event_body, context):
    f"""
    Handles retrieval of additional user information / preferences on demand.
    :param event: (Auto) JSON-encoded API Gateway pass-through.
    :param {API_USER_EMAIL}: (Req) User identifier
    :param {API_USER_SESSION_KEY}: (Req) Temporary user-specific session key for authentication
    :param context: (Auto) JSON-encoded API Gateway pass-through. Not used.
    :return: User information if request successful
    """

    # get user information
    user_info = USER_TABLE.get_item(
        Key={API_USER_EMAIL: event_body[API_USER_EMAIL]})

    # remove sensitive fields that shouldn't be returned
    cleaned_user_info = user_info['Item']
    cleaned_user_info.pop(USER_SESSION_KEYS_FIELD)
    cleaned_user_info.pop(USER_PASSWORD_FIELD)
    cleaned_user_info.pop(USER_EMAIL_FIELD)

    return APIResponseSuccess(response_content={
        "user_info": cleaned_user_info
    }).send()
Example #8
0
def search_photos(event_body, context):
    f"""
    Core photo search function, handling search and display of user-relevant photos.
    :param event: (Auto) JSON-encoded API Gateway pass-through.
    :param {API_USER_EMAIL}: (Req) User identifier
    :param {API_USER_SESSION_KEY}: (Req) Temporary user-specific session key for authentication
    :param {API_USER_LAT}: (Req) Latitude to search around
    :param {API_USER_LON}: (Req) Longitude to search around
    :param {API_USER_RADIUS}: (Req) Radius (in walking minutes) to search around current location
    :param {API_FILTER_TERMS}: (Req) Array of terms to limit results to. If blank, will search for all results.
    :param {API_SEARCH_SIZE}: (Req) Number of results to return.
    :param {API_SEARCH_START}: (Req) Hit number to return for given query.
    :param {API_LIGHTWEIGHT_FLAG}: (Optional) Flag whether or not to return only list of relevant photo IDs instead of
    all info (speed gains and data savings).
    :param context: (Auto) JSON-encoded API Gateway pass-through. Not used.
    :return: list of photo ids and fields with core data according to search results
    """

    try:
        if event_body[API_LIGHTWEIGHT_FLAG] == "True":
            lightweight_return_flag = True
        else:
            lightweight_return_flag = False
    except KeyError:
        lightweight_return_flag = False

    # determine reasonable user_radius walking distance to degree conversion
    degree_mile_conversion = 69
    walking_speed_mph = 3
    degrees_per_minute = walking_speed_mph / degree_mile_conversion / 60
    preferred_degree_radius = degrees_per_minute * float(
        event_body[API_USER_RADIUS])

    # build polygon array around user point -- 45-45-90 triangle rule
    move_dist = preferred_degree_radius * sqrt(2)
    loc_bottom_right_bound = ', '.join([
        str(float(event_body[API_USER_LAT]) - move_dist),
        str(float(event_body[API_USER_LON]) + move_dist)
    ])
    loc_top_left_bound = ', '.join([
        str(float(event_body[API_USER_LAT]) + move_dist),
        str(float(event_body[API_USER_LON]) - move_dist)
    ])

    if len(list(event_body[API_FILTER_TERMS])) == 0:
        text_query = "matchall"
    else:
        text_query = "(or '{}')".format("' '".join(
            list(event_body[API_FILTER_TERMS])))

    # Execute CloudSearch query -- return will be sorted according to ranking expression on domain
    photos_return = CLOUDSEARCH_DOMAIN.search(
        size=int(event_body[API_SEARCH_SIZE]),
        start=int(event_body[API_SEARCH_START]),
        queryParser="structured",
        filterQuery=
        f"{PHOTO_LATLON_FIELD}:['{loc_top_left_bound}', '{loc_bottom_right_bound}']",
        query=text_query,
        sort=CLOUD_SEARCH_SORT_EXPRESSION + ' desc',
    )

    # filter out all except photo ID field if lightweight flag in call
    if lightweight_return_flag:
        photos_list_return = [{
            'id': _['id']
        } for _ in photos_return['hits']['hit']]
    else:
        photos_list_return = photos_return['hits']['hit']

    # invoke function to update photo views (without waiting for return)
    LAMBDA_CLIENT.invoke(FunctionName="UpdatePhotoViews",
                         InvocationType="Event",
                         Payload=json.dumps({"photos": photos_list_return}))

    return APIResponseSuccess(response_content={
        "photos": photos_list_return
    }).send()
Example #9
0
def user_like_photo(event_body, context):
    f"""
    Handles user photo likes, updating photo like count, user record of liked photos, and logging of like event.
    :param event: (Auto) JSON-encoded API Gateway pass-through.
    :param {API_USER_EMAIL}: (Req) User identifier
    :param {API_USER_SESSION_KEY}: (Req) Temporary user-specific session key for authentication
    :param {API_PHOTO_ID}: (Req) ID of photo to be liked
    :param context: (Auto) JSON-encoded API Gateway pass-through. Not used.
    :return: number of likes under photo ID key if successful
    """

    # get photo information from database, throwing error if the item doesn't exist
    try:
        photo_info = PHOTOS_TABLE.get_item(Key={PHOTO_ID_FIELD: event_body[API_PHOTO_ID]})['Item']
    except KeyError:
        return APIResponseError(message=f"PHOTO_DOESNT_EXIST", status_code=404)

    # check attempted like isn't from photo owner
    if photo_info[PHOTO_OWNER_FIELD] == event_body[API_USER_EMAIL]:
        return APIResponseForbidden(message="USER_CANNOT_LIKE_OWN").send()

    # check user hasn't already liked photo
    current_user_data = USER_TABLE.get_item(Key={API_USER_EMAIL: event_body[API_USER_EMAIL]})
    try:
        if event_body[API_PHOTO_ID] in current_user_data['Item'][USER_PHOTO_LIKES_FIELD]:
            return APIResponseForbidden(message="LIKED_PHOTO_ALREADY", status_code=409).send()
    # catch KeyError (could occur if user has no liked photos, as is not a required field on user account creation)
    except KeyError:
        pass

    # increment photo like count -- this is set to 0 on photo upload
    like_count = photo_info[PHOTO_LIKES_FIELD] + 1

    # update likes on photo in photo table
    PHOTOS_TABLE.update_item(
        Key={PHOTO_ID_FIELD: event_body[API_PHOTO_ID]},
        UpdateExpression=f'SET {PHOTO_LIKES_FIELD} = :new_like_count',
        ExpressionAttributeValues={
            ':new_like_count': like_count
        })

    # save like to user in user table
    try:
        user_photo_likes_list = current_user_data['Item'][USER_PHOTO_LIKES_FIELD]
    except KeyError:
        user_photo_likes_list = []

    user_photo_likes_list.append(event_body[API_PHOTO_ID])

    USER_TABLE.update_item(
        Key={
            API_USER_EMAIL: event_body[API_USER_EMAIL]
        },
        UpdateExpression=f'SET {USER_PHOTO_LIKES_FIELD} = :user_photo_likes_list',
        ExpressionAttributeValues={
            ':user_photo_likes_list': user_photo_likes_list
        })

    # save like activity to history table
    postgres_db = psycopg2.connect(dbname=AWS_POSTGRES_DB, user=AWS_POSTGRES_USER, password=AWS_POSTGRES_PASSWORD,
                                   host=AWS_POSTGRES_URL)
    postgres_db.autocommit = True
    postgres_db_cursor = postgres_db.cursor(cursor_factory=RealDictCursor)

    postgres_db_cursor.execute(f"""
    INSERT INTO {LIKE_HISTORY_TABLE_NAME} ({LIKE_HISTORY_PHOTO_ID_FIELD}, {LIKE_HISTORY_USER_ID_FIELD}, 
    {LIKE_TYPE_FIELD}, {LIKE_HISTORY_DT_FIELD}) VALUES (%(photo)s, %(user)s, %(type)s, %(dt)s);""",
                               {'photo': event_body[API_PHOTO_ID], 'user': event_body[API_USER_EMAIL], 'type': 'like',
                                'dt': int(dt.datetime.strftime(dt.datetime.utcnow(), DT_FORMAT))})

    postgres_db_cursor.close()
    postgres_db.close()

    return APIResponseSuccess(response_content={event_body[API_PHOTO_ID]: like_count}).send()
Example #10
0
def user_upload_photo(event_body, context):
    f"""
    Handles user photo upload, saving file to S3, adding record to photo and user databases (and triggering update
    of CloudSearch index to make photo available for viewing).
    :param event: (Auto) JSON-encoded API Gateway pass-through.
    :param {API_USER_EMAIL}: (Req) User identifier
    :param {API_USER_SESSION_KEY}: (Req) Temporary user-specific session key for authentication
    :param {API_PHOTO_DATE}: (Req) photo capture timestamp, formatted as an integer (e.g. 20180131235959)
    :param {API_PHOTO_DATA}: (Req) Base64-encoded string of photo
    :param {API_PHOTO_LAT}: (Req) Latitude of photo capture location
    :param {API_PHOTO_LON}: (Req) Longitude of photo capture location
    :param {API_LOCATION_ID}: (Req) Google Location ID indicating where the photo was taken.
    :param {API_PHOTO_COMMENT}: (Optional) User-provided comments to accompany photo.
    :param context: (Auto) JSON-encoded API Gateway pass-through. Not used.
    :return: photo ID if successful (usable as pointer to S3 image location for display)
    """

    current_user_data = USER_TABLE.get_item(Key={API_USER_EMAIL: event_body[API_USER_EMAIL]})

    # generate photo filename (unique user id + photo timestamp)
    photo_filename = current_user_data['Item'][USER_ID_FIELD] + '_' + str(event_body[API_PHOTO_DATE]) + '.jpg'

    try:
        # check that photo doesn't look like a duplicate based on user ID and photo timestamp
        photo_info = PHOTOS_TABLE.get_item(Key={PHOTO_ID_FIELD: photo_filename})['Item']
    except KeyError:
        pass
    else:
        return APIResponse(status_code=409, message="Duplicate photo detected.").send()

    # decode from base64 and upload photo to s3 bucket, make publicly available
    photo_data_decoded = base64.b64decode(event_body[API_PHOTO_DATA])
    S3_STORAGE.upload_fileobj(Fileobj=io.BytesIO(photo_data_decoded), Bucket=S3_IMAGE_UPLOAD_BUCKET, Key=photo_filename,
                              ExtraArgs={'ACL': 'public-read'})

    photo_info_upload = dict()
    photo_info_upload[PHOTO_ID_FIELD] = photo_filename
    photo_info_upload[PHOTO_OWNER_FIELD] = event_body[API_USER_EMAIL]
    photo_info_upload[PHOTO_LIKES_FIELD] = 0
    photo_info_upload[PHOTO_DT_FIELD] = int(event_body[API_PHOTO_DATE])
    photo_info_upload[PHOTO_LATLON_FIELD] = f"{str(event_body[API_PHOTO_LAT])}, {str(event_body[API_PHOTO_LON])}"
    photo_info_upload[PHOTO_LOCATION_ID_FIELD] = event_body[API_LOCATION_ID]
    photo_info_upload[PHOTO_VIEWS_FIELD] = 0

    # load optional parameters once session authentication confirmed (exit function earlier if not)
    try:
        if event_body[PHOTO_COMMENTS_FIELD] != "":
            photo_info_upload[PHOTO_COMMENTS_FIELD] = str(event_body[PHOTO_COMMENTS_FIELD])
    except (KeyError, IndexError, json.JSONDecodeError):
        pass  # don't add a photo comments field

    # get list of photo tags from AWS Rekognition
    try:
        image_tag_response_raw = REKO_CLIENT.detect_labels(Image={"S3Object": {
            "Bucket": S3_IMAGE_UPLOAD_BUCKET,
            "Name": photo_filename}})

        # build list of labels from Rekognition response
        # Add tags as a set so that CloudSearch can search field as text-array
        photo_info_upload[PHOTO_TAGS_FIELD] = {re.sub("[^a-z]", "", label['Name'].lower()) for
                                               label in image_tag_response_raw['Labels']}

    except ClientError as e:
        print("Couldn't get image tags")
        print(e)
        photo_info_upload[PHOTO_TAGS_FIELD] = {"None"}

    try:
    # add photo details to photos table
        PHOTOS_TABLE.put_item(
            Item=photo_info_upload
        )
    except Exception as e:
        print(e)
        print(photo_info_upload)
        return APIResponseError(message="Error uploading the attached photo to DynamoDB",
                                response_content=photo_info_upload).send()

    # add image filename to list of user photos
    try:
        user_photos_list = current_user_data['Item'][USER_PHOTOS_FIELD]
    except KeyError:
        user_photos_list = []

    user_photos_list.append(photo_filename)

    # update user profile with record of photo
    USER_TABLE.update_item(
        Key={
            API_USER_EMAIL: event_body[API_USER_EMAIL]
        },
        UpdateExpression='SET {up_field} = :user_photos_list'.format(up_field=USER_PHOTOS_FIELD),
        ExpressionAttributeValues={
            ':user_photos_list': user_photos_list
        }
    )

    return APIResponseSuccess(status_code=201, response_content={"photo_id": photo_filename}).send()
Example #11
0
def user_unlike_photo(event_body, context):
    f"""
    Handles user unliking of photo, updating photo like count, user record of liked photos, and logging of unlike event.
    :param event: (Auto) JSON-encoded API Gateway pass-through.
    :param {API_USER_EMAIL}: (Req) User identifier
    :param {API_USER_SESSION_KEY}: (Req) Temporary user-specific session key for authentication
    :param {API_PHOTO_ID}: (Req) ID of photo to be liked
    :param context: (Auto) JSON-encoded API Gateway pass-through. Not used.
    :return: number of likes under photo ID key if successful
    """

    # check that user has liked photo
    current_user_data = USER_TABLE.get_item(Key={API_USER_EMAIL: event_body[API_USER_EMAIL]})

    try:
        user_photo_likes_list = current_user_data['Item'][USER_PHOTO_LIKES_FIELD]
        user_photo_likes_list.remove(event_body[API_PHOTO_ID])
    # KeyError or ValueError raised in case user doesn't have any liked photos or photo_id not in list
    except (KeyError, ValueError):
        return APIResponse(status_code=409, message="USER_HASNT_LIKED").send()

    # get photo info from database
    photo_info = PHOTOS_TABLE.get_item(Key={PHOTO_ID_FIELD: event_body[API_PHOTO_ID]})

    # decrement photo like count, catching case where likes are already at 0 (shouldn't be possible given above test)
    if photo_info['Item'][PHOTO_LIKES_FIELD] <= 0:
        return APIResponseForbidden(message="LIKE_COUNT_0").send()

    like_count = photo_info['Item'][PHOTO_LIKES_FIELD] - 1

    # update likes on photo in photo table
    PHOTOS_TABLE.update_item(
        Key={PHOTO_ID_FIELD: event_body[API_PHOTO_ID]},
        UpdateExpression='SET {pl_field} = :new_like_count'.format(pl_field=PHOTO_LIKES_FIELD),
        ExpressionAttributeValues={
            ':new_like_count': like_count
        })

    # update user record to remove photo like
    USER_TABLE.update_item(
        Key={
            API_USER_EMAIL: event_body[API_USER_EMAIL]
        },
        UpdateExpression='SET {upl_field} = :user_photo_likes_list'.format(upl_field=USER_PHOTO_LIKES_FIELD),
        ExpressionAttributeValues={
            ':user_photo_likes_list': user_photo_likes_list
        })

    # save unlike activity to like history table
    postgres_db = psycopg2.connect(dbname=AWS_POSTGRES_DB, user=AWS_POSTGRES_USER, password=AWS_POSTGRES_PASSWORD,
                                   host=AWS_POSTGRES_URL)
    postgres_db.autocommit = True
    postgres_db_cursor = postgres_db.cursor(cursor_factory=RealDictCursor)

    postgres_db_cursor.execute(f"""
        INSERT INTO {LIKE_HISTORY_TABLE_NAME} ({LIKE_HISTORY_PHOTO_ID_FIELD}, {LIKE_HISTORY_USER_ID_FIELD}, 
        {LIKE_TYPE_FIELD}, {LIKE_HISTORY_DT_FIELD}) VALUES (%(photo)s, %(user)s, %(type)s, %(dt)s);""",
                               {'photo': event_body[API_PHOTO_ID], 'user': event_body[API_USER_EMAIL], 'type': 'unlike',
                                'dt': int(dt.datetime.strftime(dt.datetime.utcnow(), DT_FORMAT))})

    postgres_db_cursor.close()
    postgres_db.close()

    return APIResponseSuccess(response_content={event_body[API_PHOTO_ID]: like_count}).send()
Example #12
0
def user_session_login(event_body, context):
    f"""
    Handles user session log-ins, generating temporary session key for further API requests. Keys auto-expire
    after a defined period, requiring a new session log in.
    :param event: (Auto) JSON-encoded API Gateway pass-through.
    :param {API_USER_EMAIL}: (Req) User identifier
    :param {API_USER_PASSWORD}: (Req) HASHED user password that will be directly stored in DB
    :param context: (Auto) JSON-encoded API Gateway pass-through. Not used.
    :return: Temporary session key under "session_key" key if successful
    """

    # get stored password for email
    db_password_response = USER_TABLE.get_item(
        Key={API_USER_EMAIL: event_body[API_USER_EMAIL]})

    # verify password
    try:
        db_password = db_password_response['Item'][API_USER_PASSWORD]
    except KeyError:
        return APIResponseUnauthorized().send()

    if event_body[API_USER_PASSWORD] != db_password:
        return APIResponseUnauthorized().send()

    # get list of current session keys
    current_user_data = USER_TABLE.get_item(
        Key={API_USER_EMAIL: event_body[API_USER_EMAIL]})
    try:
        session_keys_list = current_user_data['Item'][USER_SESSION_KEYS_FIELD]
    except KeyError:
        session_keys_list = []

    # generate and store session key in user table
    new_session_key = generate_user_key(USER_KEY_LENGTH)

    session_keys_list.append(new_session_key)

    USER_TABLE.update_item(
        Key={API_USER_EMAIL: event_body[API_USER_EMAIL]},
        UpdateExpression=f'SET {USER_SESSION_KEYS_FIELD} = :session_keys_list',
        ExpressionAttributeValues={':session_keys_list': session_keys_list})

    # add login to user login history table
    postgres_db = psycopg2.connect(dbname=AWS_POSTGRES_DB,
                                   user=AWS_POSTGRES_USER,
                                   password=AWS_POSTGRES_PASSWORD,
                                   host=AWS_POSTGRES_URL)
    postgres_db.autocommit = True
    postgres_db_cursor = postgres_db.cursor(cursor_factory=RealDictCursor)

    postgres_db_cursor.execute(
        f"""INSERT INTO {LOGIN_HISTORY_TABLE_NAME} ({LOGIN_HISTORY_DT_FIELD}, 
    {LOGIN_HISTORY_USER_FIELD}) VALUES (%(dt)s, %(user)s)""", {
            'dt': int(dt.datetime.strftime(dt.datetime.utcnow(), DT_FORMAT)),
            'user': event_body[API_USER_EMAIL]
        })

    postgres_db_cursor.close()
    postgres_db.close()

    return APIResponseSuccess(response_content={
        "session_key": new_session_key
    }).send()