示例#1
0
def apikey():
    """
    Verify OC membership and return an API key. The API key will be
    saved in the DB to verify use as well as returned upon subsequent calls
    to this endpoint with the same OC credentials.
    """
    json = request.get_json()
    email = json.get('email')
    password = json.get('password')
    is_oc_member = is_user_oc_member(email, password)

    if not is_oc_member:
        return unauthorized_response()

    try:
        # We need to check the database for an existing key
        apikey = Key.query.filter_by(email=email).first()

        # Don't return success for blacklisted keys
        if apikey and apikey.blacklisted:
            return unauthorized_response()

        if not apikey:
            # Since they're already authenticated by is_oc_user(), we know we
            # can generate an API key for them if they don't already have one
            apikey = create_new_apikey(email, db.session)
            if not apikey:
                return utils.standardize_response(status_code=500)

        logger.info(apikey.serialize)
        return utils.standardize_response(payload=dict(data=apikey.serialize),
                                          datatype="credentials")
    except Exception as e:
        logger.exception(e)
        return utils.standardize_response(status_code=500)
示例#2
0
def get_languages():
    try:
        languages = Language.query.all()

        if not languages:
            return redirect('/404')
        language_list = [language.serialize for language in languages]
        details = {'details': {'total_count': len(languages)}}
    except Exception as e:
        logger.exception(e)
        return utils.standardize_response(status_code=500)

    return utils.standardize_response(payload=dict(data=language_list,
                                                   **details),
                                      datatype="languages")
示例#3
0
def get_categories():
    try:
        categories = Category.query.all()

        if not categories:
            return redirect('/404')
        category_list = [
            category.serialize for category in categories
        ]
        details = {'details': {'total_count': len(categories)}}
    except Exception as e:
        logger.exception(e)
        return utils.standardize_response(status_code=500)

    return utils.standardize_response(payload=dict(
        data=category_list,
        **details),
        datatype="categories")
示例#4
0
def get_categories():
    try:
        category_paginator = utils.Paginator(Config.CATEGORY_PAGINATOR, request)
        query = Category.query

        paginated_categories = category_paginator.paginated_data(query)
        if not paginated_categories:
            return redirect('/404')
        category_list = [
            category.serialize for category in paginated_categories.items
        ]
        pagination_details = category_paginator.pagination_details(paginated_categories)
    except Exception as e:
        logger.exception(e)
        return utils.standardize_response(status_code=500)

    return utils.standardize_response(payload=dict(
        data=category_list,
        **pagination_details))
示例#5
0
def get_languages():
    language_paginator = utils.Paginator(Config.LANGUAGE_PAGINATOR, request)
    query = Language.query

    try:
        paginated_languages = language_paginator.paginated_data(query)
        if not paginated_languages:
            return redirect('/404')
        language_list = [
            language.serialize for language in paginated_languages.items
        ]
        pagination_details = language_paginator.pagination_details(
            paginated_languages)
    except Exception as e:
        logger.exception(e)
        return utils.standardize_response(status_code=500)

    return utils.standardize_response(
        payload=dict(data=language_list, **pagination_details))
def update_resource(id, json, db):
    resource = Resource.query.get(id)

    if not resource:
        return redirect('/404')

    langs, categ = get_attributes(json)
    index_object = {'objectID': id}

    try:
        logger.info(f"Updating resource. Old data: {resource.serialize}")
        if json.get('languages'):
            resource.languages = langs
            index_object['languages'] = resource.serialize['languages']
        if json.get('category'):
            resource.category = categ
            index_object['category'] = categ.name
        if json.get('name'):
            resource.name = json.get('name')
            index_object['name'] = json.get('name')
        if json.get('url'):
            resource.url = json.get('url')
            index_object['url'] = json.get('url')
        if 'paid' in json:
            paid = json.get('paid')

            # Converts "false" and "true" to their bool
            if type(paid) is str and paid.lower() in ["true", "false"]:
                paid = paid.lower().strip() == "true"
            resource.paid = paid
            index_object['paid'] = paid
        if 'notes' in json:
            resource.notes = json.get('notes')
            index_object['notes'] = json.get('notes')

        try:
            index.partial_update_object(index_object)

        except (AlgoliaUnreachableHostException, AlgoliaException) as e:
            if environ.get("FLASK_ENV") != 'development':
                logger.exception(e)
                msg = f"Algolia failed to update index for resource '{resource.name}'"
                logger.warn(msg)
                error = {'errors': [{"algolia-failed": {"message": msg}}]}
                return utils.standardize_response(payload=error,
                                                  status_code=500)

        # Wait to commit the changes until we know that Aloglia was updated
        db.session.commit()

        return utils.standardize_response(payload=dict(
            data=resource.serialize))

    except IntegrityError as e:
        logger.exception(e)
        return utils.standardize_response(status_code=422)

    except Exception as e:
        logger.exception(e)
        return utils.standardize_response(status_code=500)
示例#7
0
def search_results():
    term = request.args.get('q', '', str)
    page = request.args.get('page', 0, int)
    page_size = request.args.get('page_size', Config.RESOURCE_PAGINATOR.per_page, int)

    # Fetch the filter params from the url, if they were provided.
    free = request.args.get('free')
    category = request.args.get('category')
    languages = request.args.getlist('languages')
    filters = []

    # Filter on free
    if free:
        free = free.lower()
        # algolia filters boolean attributes with either 0 or 1
        if free == 'true':
            filters.append('free=1')
        elif free == 'false':
            filters.append('free=0')

    # Filter on category
    if category:
        # to not let double quotes conflict with algolia filter format
        category = category.replace('"', "'")

        filters.append(
            f'category: "{category}"'
        )

    # Filter on languages
    if languages and '' not in languages:
        for i, _ in enumerate(languages):
            # to not let double quotes conflict with algolia filter format
            languages[i] = 'languages:"{}"'.format(languages[i].replace('"', "'"))

        # joining all possible language values to algolia filter query
        filters.append(f"( {' OR '.join(languages)} )")

    try:
        search_result = index.search(f'{term}', {
            'filters': " AND ".join(filters),
            'page': page,
            'hitsPerPage': page_size
        })

    except (AlgoliaUnreachableHostException, AlgoliaException) as e:
        logger.exception(e)
        msg = "Failed to get resources from Algolia"
        logger.warn(msg)
        error = {'errors': [{"algolia-failed": {"message": msg}}]}
        return utils.standardize_response(payload=error, status_code=500)

    if page >= int(search_result['nbPages']):
        return redirect('/404')

    results = [utils.format_resource_search(result) for result in search_result['hits']]

    details = {
        "details": {
            "page": search_result['page'],
            "number_of_pages": search_result['nbPages'],
            "records_per_page": search_result['hitsPerPage'],
            "total_count": search_result['nbHits'],
        }
    }
    return utils.standardize_response(
        payload=dict(data=results, **details),
        datatype="resources")
def get_resources():
    """
    Gets a paginated list of resources.

    If the URL parameters `languages` or `category` are found
    in the request, the list will be filtered by these parameters.

    The filters are case insensitive.
    """
    resource_paginator = utils.Paginator(Config.RESOURCE_PAGINATOR, request)

    # Fetch the filter params from the url, if they were provided.
    languages = request.args.getlist('languages')
    category = request.args.get('category')
    updated_after = request.args.get('updated_after')
    paid = request.args.get('paid')

    q = Resource.query

    # Filter on languages
    if languages:
        # Take the list of languages they pass in, join them all with OR
        q = q.filter(
            or_(*map(Resource.languages.any, map(Language.name.ilike,
                                                 languages))))

    # Filter on category
    if category:
        q = q.filter(
            Resource.category.has(
                func.lower(Category.name) == category.lower()))

    # Filter on updated_after
    if updated_after:
        try:
            uaDate = parser.parse(updated_after)
            if uaDate > datetime.now():
                raise Exception("updated_after greater than today's date")
            uaDate = uaDate.strftime("%Y-%m-%d")
        except Exception as e:
            logger.exception(e)
            message = 'The value for "updated_after" is invalid'
            res = {"errors": {"unprocessable-entity": {"message": message}}}
            return utils.standardize_response(payload=res, status_code=422)

        q = q.filter(
            or_(Resource.created_at >= uaDate,
                Resource.last_updated >= uaDate))

    # Filter on paid
    if isinstance(paid, str) and paid.lower() in ['true', 'false']:
        paidAsBool = paid.lower() == 'true'
        q = q.filter(Resource.paid == paidAsBool)

    try:
        paginated_resources = resource_paginator.paginated_data(q)
        if not paginated_resources:
            return redirect('/404')
        resource_list = [
            resource.serialize for resource in paginated_resources.items
        ]
        pagination_details = resource_paginator.pagination_details(
            paginated_resources)
    except Exception as e:
        logger.exception(e)
        return utils.standardize_response(status_code=500)

    return utils.standardize_response(payload=dict(data=resource_list,
                                                   **pagination_details),
                                      datatype="resources")
def update_resource(id, json, db):
    resource = Resource.query.get(id)
    api_key = g.auth_key.apikey

    if not resource:
        return redirect('/404')

    langs, categ = get_attributes(json)
    index_object = {'objectID': id}

    def get_unique_resource_categories_as_strings():
        resources = Resource.query.all()
        return {resource.category.name for resource in resources}

    def get_unique_resource_languages_as_strings():
        resources = Resource.query.all()
        return {
            language.name
            for resource in resources for language in resource.languages
        }

    try:
        logger.info(f"Updating resource. Old data: "
                    f"{json_module.dumps(resource.serialize(api_key))}")
        if json.get('languages') is not None:
            old_languages = resource.languages[:]
            resource.languages = langs
            index_object['languages'] = resource.serialize(
                api_key)['languages']
            resource_languages = get_unique_resource_languages_as_strings()
            for language in old_languages:
                if language.name not in resource_languages:
                    db.session.delete(language)
        if json.get('category'):
            old_category = resource.category
            resource.category = categ
            index_object['category'] = categ.name
            resource_categories = get_unique_resource_categories_as_strings()
            if old_category.name not in resource_categories:
                db.session.delete(old_category)
        if json.get('name'):
            resource.name = json.get('name')
            index_object['name'] = json.get('name')
        if json.get('url'):
            resource.url = json.get('url')
            index_object['url'] = json.get('url')
        if 'free' in json:
            free = ensure_bool(json.get('free'))
            resource.free = free
            index_object['free'] = free
        if 'notes' in json:
            resource.notes = json.get('notes')
            index_object['notes'] = json.get('notes')

        try:
            index.partial_update_object(index_object)

        except (AlgoliaUnreachableHostException, AlgoliaException) as e:
            if environ.get("FLASK_ENV") != 'development':
                logger.exception(e)
                msg = f"Algolia failed to update index for resource '{resource.name}'"
                logger.warn(msg)
                error = {'errors': [{"algolia-failed": {"message": msg}}]}
                return utils.standardize_response(payload=error,
                                                  status_code=500)

        # Wait to commit the changes until we know that Aloglia was updated
        db.session.commit()

        return utils.standardize_response(
            payload=dict(data=resource.serialize(api_key)),
            datatype="resource")

    except IntegrityError as e:
        logger.exception(e)
        return utils.standardize_response(status_code=422)

    except Exception as e:
        logger.exception(e)
        return utils.standardize_response(status_code=500)
示例#10
0
def create_resources(json, db):
    try:
        # Resources to return in the response
        created_resources = []
        # Serialized Resources to send to Algolia
        created_resources_algolia = []
        # Resource IDs to delete if Algolia fails to index
        resource_id_cache = []

        # Create each Resource in the database one by one
        for resource in json:
            langs, categ = get_attributes(resource)
            paid_bool = ensure_bool(resource.get('paid'))
            new_resource = Resource(name=resource.get('name'),
                                    url=resource.get('url'),
                                    category=categ,
                                    languages=langs,
                                    paid=paid_bool,
                                    notes=resource.get('notes'))

            try:
                db.session.add(new_resource)
                db.session.commit()
                created_resources_algolia.append(
                    new_resource.serialize_algolia_search)
                resource_id_cache.append(new_resource.id)

            except IntegrityError as e:
                logger.exception(e)
                return utils.standardize_response(status_code=422)

            except Exception as e:
                logger.exception(e)
                return utils.standardize_response(status_code=500)

            created_resources.append(new_resource.serialize)

        # Take all the created resources and save them in Algolia with one API call
        try:
            index.save_objects(created_resources_algolia)

        except (AlgoliaUnreachableHostException, AlgoliaException) as e:
            if (environ.get("FLASK_ENV") != 'development'):
                logger.exception(e)

                # Remove created resources from the db to stay in sync with Algolia
                for res_id in resource_id_cache:
                    res = Resource.query.get(res_id)
                    res.languages.clear()
                    db.session.delete(res)
                db.session.commit()

                msg = "Algolia failed to index resources"
                error = {'errors': [{"algolia-failed": {"message": msg}}]}
                return utils.standardize_response(payload=error,
                                                  status_code=500)

        # Success
        return utils.standardize_response(payload=dict(data=created_resources),
                                          datatype="resources")
    except Exception as e:
        logger.exception(e)
        return utils.standardize_response(status_code=500)