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 page_not_found(e): errors = [ { "status": 404, "code": "not-found" } ] # Set the 404 status explicitly return standardize_response(None, errors, "not found"), 404
def wrapper(*args, **kwargs): apikey = request.headers.get('x-apikey') key = Key.query.filter_by(apikey=apikey).first() if not key: return standardize_response(status_code=401) log_request(request, key) return func(*args, **kwargs)
def internal_server_error(e): errors = [ { "status": 500, "code": "internal-server-error" } ] # Set the 500 status explicitly return standardize_response(None, errors, "internal server error"), 500
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))
def wrapper(*args, **kwargs): apikey = request.headers.get('x-apikey') key = Key.query.filter_by(apikey=apikey).first() if not key: errors = [{"code": "not-authorized"}] return standardize_response(None, errors, "not authorized", 401) log_request(request, key) return func(*args, **kwargs)
def add_click(id): resource = Resource.query.get(id) if not resource: return redirect('/404') initial_count = getattr(resource, 'times_clicked') setattr(resource, 'times_clicked', initial_count + 1) db.session.commit() return utils.standardize_response(payload=dict(data=resource.serialize))
def update_votes(id, vote_direction): resource = Resource.query.get(id) if not resource: return redirect('/404') initial_count = getattr(resource, vote_direction) setattr(resource, vote_direction, initial_count + 1) db.session.commit() return utils.standardize_response(payload=dict(data=resource.serialize))
def post_resources(): json = request.get_json() if not isinstance(json, list): return wrong_type("list of resources objects", type(json)) validation_errors = validate_resource_list(request.method, json) if validation_errors: return utils.standardize_response(payload=validation_errors, status_code=422) return create_resources(json, db)
def put_resource(id): json = request.get_json() if not isinstance(json, dict): return wrong_type("resource object", type(json)) validation_errors = validate_resource(request.method, json, id) if validation_errors: errors = {"errors": validation_errors} return utils.standardize_response(payload=errors, status_code=422) return update_resource(id, request.get_json(), db)
def get_resources(): """ Gets a paginated list of resources. If the URL parameters `language` or `category` are found in the request, the list will be filtered by these parameters. The filters are case insensitive. """ resource_paginator = Paginator(Config.RESOURCE_PAGINATOR, request) # Fetch the filter params from the url, if they were provided. language = request.args.get('language') category = request.args.get('category') # Filter on language if language and not category: query = Resource.query.filter( Resource.languages.any( Language.name.ilike(language) ) ) # Filter on category elif category and not language: query = Resource.query.filter( Resource.category.has( func.lower(Category.name) == category.lower() ) ) # Filter on both elif category and language: query = Resource.query.filter( and_( Resource.languages.any( Language.name.ilike(language) ), Resource.category.has( func.lower(Category.name) == category.lower() ) ) ) # No filters else: query = Resource.query resource_list = [ resource.serialize for resource in resource_paginator.items(query) ] return standardize_response(resource_list, None, "ok")
def add_click(id): resource = Resource.query.get(id) api_key = g.auth_key.apikey if g.auth_key else None if not resource: return redirect('/404') initial_count = getattr(resource, 'times_clicked') setattr(resource, 'times_clicked', initial_count + 1) db.session.commit() return utils.standardize_response( payload=dict(data=resource.serialize(api_key)), datatype="resource")
def add_click(id): try: resource = Resource.query.get(id) if not resource: return redirect('/404') except NoResultFound as e: print_tb(e.__traceback__) logger.exception(e) return redirect('/404') except Exception as e: print_tb(e.__traceback__) logger.exception(e) return standardize_response(status_code=500) initial_count = getattr(resource, 'times_clicked') setattr(resource, 'times_clicked', initial_count + 1) db.session.commit() return standardize_response(payload=dict(data=resource.serialize))
def create_resource(json, db): langs, categ = get_attributes(json) new_resource = Resource(name=json.get('name'), url=json.get('url'), category=categ, languages=langs, paid=json.get('paid'), notes=json.get('notes')) db.session.add(new_resource) db.session.commit() return standardize_response(new_resource.serialize, None, "ok")
def get_resource(id): resource = None try: resource = Resource.query.get(id) except NoResultFound as e: print_tb(e.__traceback__) logger.exception(e) return redirect('/404') if resource: return standardize_response(payload=dict(data=(resource.serialize))) return redirect('/404')
def wrong_type(type_accepted, type_provided): types = { dict: "object", int: "int", list: "array", float: "number", bool: "boolean", str: "string" } json_type = types[type_provided] msg = f"Expected {type_accepted}, but found {json_type}" validation_errors = {"errors": {"invalid-type": {"message": msg}}} return standardize_response(payload=validation_errors, status_code=422)
def get_categories(): try: category_paginator = Paginator(Config.CATEGORY_PAGINATOR, request) query = Category.query category_list = [ category.serialize for category in category_paginator.items(query) ] except Exception as e: print_tb(e.__traceback__) print(e) category_list = [] finally: return standardize_response(category_list, None, "ok")
def set_resource(id, json, db): resource = None resource = Resource.query.get(id) langs, categ = get_attributes(json) if resource: if json.get('languages'): resource.languages = langs if json.get('category'): resource.category = categ if json.get('name'): resource.name = json.get('name') if json.get('url'): resource.url = json.get('url') if 'paid' in json: resource.paid = json.get('paid') if 'notes' in json: resource.notes = json.get('notes') db.session.commit() return standardize_response(resource.serialize, None, "ok") else: return standardize_response({}, None, "ok")
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 denied keys if apikey and apikey.denied: 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)
def create_resource(json, db): langs, categ = get_attributes(json) new_resource = Resource(name=json.get('name'), url=json.get('url'), category=categ, languages=langs, paid=json.get('paid'), notes=json.get('notes')) try: db.session.add(new_resource) db.session.commit() index.save_object(new_resource.serialize_algolia_search) except (AlgoliaUnreachableHostException, AlgoliaException) as e: logger.exception(e) print(f"Algolia failed to index new resource '{new_resource.name}'") except Exception as e: logger.exception(e) return utils.standardize_response(status_code=500) return utils.standardize_response(payload=dict( data=new_resource.serialize))
def pay(self, data, user): methods = { 'briantree': payment.Briantree(), 'stripe': payment.Stripe() } vault = Vault.query.filter_by(user_id=user.id).filter_by( uuid=data['token']).first() data['card'] = json.loads(self.decode_token(user, vault.card_token)) status = methods[self.gateway].pay(data) response = standardize_response(self.gateway, status) if response == True: return {"status": "success", "message": "charge successful"} elif response == False: return {"status": "error", "message": "charge failure"}, 500 else: # do further processing on the transaction return further_processing(self.gateway, response)
def update_votes(id, vote_direction_attribute): resource = Resource.query.get(id) if not resource: return redirect('/404') initial_count = getattr(resource, vote_direction_attribute) vote_direction = vote_direction_attribute[:-1] opposite_direction_attribute = 'downvotes' \ if vote_direction_attribute == 'upvotes' else 'upvotes' opposite_direction = opposite_direction_attribute[:-1] opposite_count = getattr(resource, opposite_direction_attribute) api_key = g.auth_key.apikey vote_info = VoteInformation.query.get({ 'voter_apikey': api_key, 'resource_id': id }) if vote_info is None: voter = Key.query.filter_by(apikey=api_key).first() new_vote_info = VoteInformation(voter_apikey=api_key, resource_id=resource.id, current_direction=vote_direction) new_vote_info.voter = voter resource.voters.append(new_vote_info) setattr(resource, vote_direction_attribute, initial_count + 1) else: if vote_info.current_direction == vote_direction: setattr(resource, vote_direction_attribute, initial_count - 1) setattr(vote_info, 'current_direction', None) else: setattr(resource, opposite_direction_attribute, opposite_count - 1) \ if vote_info.current_direction == opposite_direction else None setattr(resource, vote_direction_attribute, initial_count + 1) setattr(vote_info, 'current_direction', vote_direction) db.session.commit() return utils.standardize_response( payload=dict(data=resource.serialize(api_key)), datatype="resource")
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)
def rotate_apikey(): new_key = rotate_key(g.auth_key, db.session) if not new_key: return utils.standardize_response(status_code=500) return utils.standardize_response(payload=dict(data=new_key.serialize), datatype="credentials")
def post_resources(): validation_errors = validate_resource(request.get_json()) if validation_errors: return standardize_response(payload=validation_errors, status_code=422) return create_resource(request.get_json(), db)
def get_resources(): """ Gets a paginated list of resources. If the URL parameters `language` or `category` are found in the request, the list will be filtered by these parameters. The filters are case insensitive. """ resource_paginator = Paginator(Config.RESOURCE_PAGINATOR, request) # Fetch the filter params from the url, if they were provided. language = request.args.get('language') category = request.args.get('category') updated_after = request.args.get('updated_after') paid = request.args.get('paid') q = Resource.query # Filter on language if language: q = q.filter(Resource.languages.any(Language.name.ilike(language))) # 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 = dict(errors=[{ 'code': 'unprocessable-entity', 'message': message }]) return 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): 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 standardize_response(status_code=500) return standardize_response( payload=dict(data=resource_list, **pagination_details))
def unauthorized_response(): message = "The email or password you submitted is incorrect " \ "or your account is not allowed api access" payload = {'errors': {"unauthorized": {"message": message}}} return utils.standardize_response(payload=payload, status_code=401)
def response(*args): return standardize_response(dict(), 1337)
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) # Order by "getting started" category if not languages and not category and paid is None: show_first = Category.query.filter( Category.name == "Getting Started").first() clause = (f" CASE resource.category_id" f" WHEN {show_first.id} THEN 1" f" ELSE 2" f" END, id") q = q.order_by(text(clause)) 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 ] details = resource_paginator.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, **details), datatype="resources")
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")