def delete(self, *keys): key_tokens = [self._tokenize(key) for key in keys] response = self._redis.delete(*key_tokens) for key_token in key_tokens: logger.debug('Delete cache key:%s' % key_token) return response
def reviews_post(request): """ creates new business review **authentication required with access_token** errors ====== * status 400 - if failed to create business returns ======= :: { "id": "" } """ business_id = request.matchdict['business_id'] business = Business.get_by_id(business_id) user = request.validated['user'] text = request.validated['text'] rating = request.validated['rating'] tags = request.validated['tags'] if tags: tags = tags.split(':') else: tags = [] if business and user: review = Review.create(user.id, business, rating, text, tags) response_body = { 'id': review.id, } # kill cache cache_key = 'business:{}*'.format(business.id) redis_cache.delete_pattern(cache_key) else: logger.debug('Failed to create review for business:{}'.format(business_id)) request.response.status_int = 400 response_body = { 'status': 'error', 'message': 'failed to create review for business' } request.response.body = json.dumps(response_body, cls=ComplexEncoder) request.response.content_type = 'application/json' return request.response
def reviews_get(request): """ Returns the business reviews errors ====== * status 404 - if the business can't be found returns ======= :: { "reviews": [ { "rating": 5, "modified": "2015-08-10T00:20:17.753000", "id": "55c7ee41fad9b43993d71919", "user_id": "55c7ee3dfad9b43993d7190e", "reviewed_id": "55c7ee3efad9b43993d7190f", "tags": [ "awesome" ], "text": "This is awesome.", "created": "2015-08-10T00:20:17.753000", "reviewed_collection": "businesses" } ] } """ business_id = request.matchdict['business_id'] business = Business.get_by_id(business_id) if business: response_body = { 'reviews': Review.reviews_for_reviewed(business.collection, business.id) } logger.debug('Retrieved business:{} reviews'.format(business.id)) else: logger.debug('Failed to retrieve business:{} reviews'.format(business_id)) request.response.status_int = 404 response_body = { 'status': 'error', 'message': 'failed to find business' } request.response.body = json.dumps(response_body, cls=ComplexEncoder) request.response.content_type = 'application/json' return request.response
def setex(self, key, value, seconds): key_token = self._tokenize(key) # if value is not string then pickle it if isinstance(value, basestring): stored_value = value else: pickled_value = pickle.dumps(value) stored_value = '%s%s' % (self._pickle_token, pickled_value) response = self._redis.setex(key_token, stored_value, seconds) logger.debug('Set cache key:%s' % key_token) return response
def save(self): """ saves the list """ saved = False now = datetime.utcnow() self.modified = now # if _id is in keys then the document has been saved, otherwise it's a new document if '_id' in self.data.keys(): # the list needs updating ret = mongodb[self.collection].update({'_id': self.data['_id']}, self.data) if ret['n']: logger.debug('saved existing {} id:{}'.format(self.collection, self.id)) saved = True else: logger.debug('failed to save existing {} id:{}'.format(self.collection, self.id)) else: # new list ret = mongodb[self.collection].insert(self.data) if ret: logger.debug('saved new {} id:{}'.format(self.collection, self.id)) saved = True else: logger.debug('failed to save new {}'.format(self.collection)) return saved
def businesses_post(request): """ create a new business **requires authentication with an access_token** errors ====== * status 500 - if the user can't be saved returns ======= :: { "id": "" } """ user = request.validated['user'] name = request.validated['name'] address = { 'street1': request.validated['street1'], 'street2': request.validated['street2'], 'city': request.validated['city'], 'state': request.validated['state'], 'postal_code': request.validated['postal_code'], } business = Business.create(name, address) if business: logger.debug('Created new business:{}'.format(business.id)) response_body = { 'id': business.id } else: logger.debug('Failed to create new business.') requests.response.status_int = 500 response_body = { 'status': 'error', 'message': 'failed to create new business', } request.response.body = json.dumps(response_body, cls=ComplexEncoder) request.response.content_type = 'application/json' return request.response
def delete(self): """ delete the document """ deleted = False ret = mongodb[self.collection].remove({'_id': self.data['_id']}) if ret['n']: logger.debug('deleted {} id:{}'.format(self.collection, self.id)) deleted = True else: logger.debug('failed to delete {} id:{}'.format(self.collection, self.id)) return deleted
def review_get(request): """ Returns the business review errors ====== * status 404 - if the review couldn't be found returns ======= :: { "rating": 5, "modified": "2015-08-10T00:20:17.753000", "id": "55c7ee41fad9b43993d71919", "user_id": "55c7ee3dfad9b43993d7190e", "reviewed_id": "55c7ee3efad9b43993d7190f", "tags": [ "awesome" ], "text": "This is awesome.", "created": "2015-08-10T00:20:17.753000", "reviewed_collection": "businesses" } """ business_id = request.matchdict['business_id'] review_id = request.matchdict['review_id'] business = Business.get_by_id(business_id) review = Review.get_by_id(review_id) if business and review and business.id == review.reviewed_id: response_body = review.json logger.debug('Retrieved business:{} review:{}'.format(business.id, review.id)) else: logger.debug('Failed to retrieve business:{} review:'.format(business_id, review_id)) request.response.status_int = 404 response_body = json.dumps({ 'status': 'error', 'message': 'failed to find business review' }) request.response.body = response_body request.response.content_type = 'application/json' return request.response
def delete_pattern(self, pattern): """ deletes all keys that matches the pattern """ key_tokens = self.keys(pattern) if key_tokens: response = self._redis.delete(*key_tokens) else: response = True for key_token in key_tokens: logger.debug('Delete cache key:%s' % key_token) return response
def update_business(event): """ compiles rating and tags for a business when a review is updated """ from reviews.models import Review review = event.review business = Business.get_by_id(review.reviewed_id) if business: business.rating = Review.rating_for_reviewed(review.reviewed_id) business.tags = Review.tags_for_reviewed(review.reviewed_id) business.save() logger.debug('updated business:{} reviews'.format(business.id)) else: logger.debug('failed to update business:{} reviews'.format(review.reviewd_id))
def set(self, key, value): key_token = self._tokenize(key) # if value is not string then pickle it if isinstance(value, basestring): stored_value = value else: pickled_value = pickle.dumps(value) stored_value = '%s%s' % (self._pickle_token, pickled_value) # set default timer, ignore if not set if self._timeout: response = self._redis.setex(key_token, stored_value, self._timeout) else: response = self._redis.set(key_token, stored_value) logger.debug('Set cache key:%s' % key_token) return response
def get(self, key): key_token = self._tokenize(key) value = None stored_value = self._redis.get(key_token) if stored_value: # check if the stored value is pickled if stored_value.find(self._pickle_token) == 0: pickled_value = stored_value.replace(self._pickle_token, '', 1) value = pickle.loads(pickled_value) else: value = stored_value logger.debug('Got cache key:%s' % key_token) else: logger.debug('Failed to get cache key:%s' % key_token) return value
def users_post(request): """ an endpoint to create new users new users are not active by default TODO: have a user activation process errors ====== * status 400 - if failed returns ======= :: { "id": "" } """ email = request.validated['email'] password = request.validated['password'] user = User.create(email, password) if user: # TODO: send activation email logger.debug('new user created') response_body = json.dumps({'id': user.json['id']}) else: logger.debug('failed to create new user') request.response.status_int = 400 response_body = json.dumps({ 'status': 'error', 'message': 'failed to create new user' }) request.response.body = response_body request.response.content_type = 'application/json' return request.response
def auth_post(request): """ An endpoint to authenticate users and retrieve their access token. The access token is needed to authenticate requests that needs authorization:: /endpont?access_token=<access token> returns ======= returns 401 auth error if fails, otherswise returns:: { 'access_token': '', 'user_id': '' } """ email = request.validated['email'] password = request.validated['password'] user = User.authenticate_user(email, password) response_body = {} if user: # user found and authenticated logger.debug('user:{} authenticated'.format(email)) access_token = create_access_token(user) response_body = json.dumps({ 'access_token': access_token, 'user_id': str(user.id), }) else: # user not found or authenticated logger.debug('user:{} failed authentication'.format(email)) request.response.status_int = 401 response_body = json.dumps({ 'status': 'error', 'message': 'user failed to authenticate', }) request.response.body = response_body request.response.content_type = 'application/json' return request.response
def user_get(request): """ get user errors ====== * status 404 - if the user couldn't be found returns ======= :: { "modified": "2015-08-10T00:20:13.708000", "active": "false", "id": "55c7ee3dfad9b43993d7190e", "email": "*****@*****.**", "created": "2015-08-10T00:20:12.882000" } """ user_id = request.matchdict['user_id'] user = User.get_by_id(user_id) if user: logger.debug('got user:{}'.format(user_id)) response_body = user.json else: logger.debug('could not find user:{}'.format(user_id)) request.response.status_int = 404 response_body = json.dumps({ 'status': 'error', 'message': 'user does not exist' }) request.response.body = response_body request.response.content_type = 'application/json' return request.response
def validate_access_token(access_token): """ This request requires validation. To get an access token use the ``/authenticate`` endpoint. """ """ Check to see if the access_token is valid The access token will invalidate if the user changes their password. parameters ========== * access_token - a token created with create_access_token returns ======= returns True or False depending on if the access_token is valid """ from settings import config validated = False try: decoded = jwt.decode(access_token, config.get('pepper', ''), algorithms=['HS256']) except jwt.DecodeError: logger.debug('jwt DecodeError') else: now = datetime.utcnow() then = datetime.fromtimestamp(decoded['iat']) age = now - then user = User.get_by_id(decoded['user_id']) # TODO: make the age configurable # for now the access_token is valid for 5 hours if age.seconds > 60 * 60 * 5: logger.debug('stale access token, timestamp expired') elif user and decoded['password'] == user.password: validated = { 'user': user, } logger.debug('Valid access token for user:{}'.format(user.id)) else: logger.debug('access token failed to validate') return validated
def valid_key(request): """ Check to see if a valid key has been sent If the key is valid it sets the user on the request.validated object ``request.validated['user']`` """ access_token = request.GET.get('access_token') validated = False # check which mode to authorize access if access_token: validated = validate_access_token(access_token) else: logger.debug('Invalid Access Token') raise Http401() # check if validation passed if validated: request.validated['user'] = validated.get('user') else: raise Http401()
def get_by_id(cls, id): """ load the document by the id """ obj = None # force id into ObjectId try: _id = ObjectId(id) except InvalidId: mongo_obj = None logger.debug('invalid ObjectId id:{}'.format(id)) else: # get the mongo_obj mongo_obj = mongodb[cls.collection].find_one({'_id': _id}) # initialize the class if mongo_obj was retrieved if mongo_obj: obj = cls(mongo=mongo_obj) else: logger.debug('did not find {} id:{}'.format(cls.collection, id)) return obj
def user_password_put(request): """ Update user's password errors ====== * status 400 - if failed to update user * status 404 - if failed to find user returns ======= :: { 'status': 'success', 'message': 'user password updated' } """ user_id = request.matchdict['user_id'] user = User.get_by_id(user_id) if user: # check for fields to update user.password = request.validated['password'] # save if user.save(): logger.debug('user:{} updated'.format(user_id)) response_body = json.dumps({ 'status': 'success', 'message': 'user password updated' }) else: logger.debug('failed to save user:{} password'.format(user_id)) request.response.status_int = 400 response_body = json.dumps({ 'status': 'error', 'message': 'failed to update user' }) else: logger.debug('failed to update user:{} password, user not found'.format(user_id)) request.response.status_int = 404 response_body = json.dumps({ 'status': 'error', 'message': 'could not find user' }) request.response.body = response_body request.response.content_type = 'application/json' return request.response
def user_put(request): """ Update user errors ====== * status 400 - if failed to update user * status 404 - if user isn't found returns ======= :: { 'status': 'success', 'message': 'user updated' } """ user_id = request.matchdict['user_id'] user = User.get_by_id(user_id) if user: user.active = request.validated['active'] user.email = request.validated['email'] # save if user.save(): logger.debug('user:{} updated'.format(user_id)) response_body = json.dumps({ 'status': 'success', 'message': 'user updated' }) else: logger.debug('failed to save user:{}'.format(user_id)) request.response.status_int = 400 response_body = json.dumps({ 'status': 'error', 'message': 'failed to update user' }) else: logger.debug('failed to update user:{} user not found'.format(user_id)) request.response.status_int = 404 response_body = json.dumps({ 'status': 'error', 'message': 'could not find user' }) request.response.body = response_body request.response.content_type = 'application/json' return request.response
def add_endpoints(app): logger.debug("Adding endpoint routes.") app.add_url_rule('/', 'root', root) app.add_url_rule('/approve', 'approve', approve, methods=['POST']) app.add_url_rule('/token', 'token', token, methods=['GET', 'POST']) app.add_url_rule("/authorize", "authorize", authorize, methods=['GET', 'POST'])
def businesses_get(request): """ Returns the businesses location format = [lng, lat] use limit and offset to paginate:: http://localhost:8000/api/v1/businesses?limit=100&offset=0 to restrict results withing a distance from a location use lat, lng, distance, and units:: http://localhost:8000/api/v1/businesses?lng=-73.988395&lat=40.7461666&distance=3 returns ======= :: { "businesses": [{ "rating": 2.25, "modified": "2015-08-10T00:20:14.370000", "address": { "street1": "Time Square", "postal_code": "", "street2": "", "city": "New York", "state": "NY" }, "id": "55c7ee3efad9b43993d7190f", "location": [ -73.985131, 40.758895 ], "tags": [ "hello", "world", "example", "awesome" ], "name": "Time Square", "created": "2015-08-10T00:20:13.760000" }], "count": 1 } """ location = [] lat = request.validated['lat'] lng = request.validated['lng'] distance = request.validated['distance'] units = request.validated['units'] limit = request.validated['limit'] offset = request.validated['offset'] # set location only if both lat and lng if lat and lng: location = [lng, lat] businesses = Business.businesses(location=location, distance=distance, units=units, limit=limit, offset=offset) # get business count to help pagination if location: # TODO: this count is wrong when location limiting businesses_count = len(businesses) else: businesses_count = Business.count() businesses_jsonable = [] for business in businesses: businesses_jsonable.append(business.toJSON()) response_body = { 'businesses': businesses_jsonable, 'count': businesses_count, } logger.debug('Retrieved businesses') request.response.body = json.dumps(response_body, cls=ComplexEncoder) request.response.content_type = 'application/json' return request.response
def business_get(request): """ Returns the business errors ====== * status 404 - if the business can't be found returns ======= /businesses/<id>?reviews=true :: { "rating": 5, "modified": "2015-08-10T00:20:14.370000", "address": { "street1": "Time Square", "postal_code": "", "street2": "", "city": "New York", "state": "NY" }, "id": "55c7ee3efad9b43993d7190f", "location": [ -73.985131, 40.758895 ], "reviews": [ { "rating": 5, "modified": "2015-08-10T00:20:17.753000", "id": "55c7ee41fad9b43993d71919", "user_id": "55c7ee3dfad9b43993d7190e", "reviewed_id": "55c7ee3efad9b43993d7190f", "tags": [ "awesome" ], "text": "This is awesome.", "created": "2015-08-10T00:20:17.753000", "reviewed_collection": "businesses" } ], "tags": [ "awesome" ], "name": "Time Square", "created": "2015-08-10T00:20:13.760000" } """ business_id = request.matchdict['business_id'] include_reviews = request.validated['reviews'] business = Business.get_by_id(business_id) # a light redis check is an example of how queries can be further optimized if include_reviews: cache_key = 'business:{}:reviews-true'.format(business_id) else: cache_key = 'business:{}:reviews-false'.format(business_id) cached = redis_cache.get(cache_key) if cached and business: response_body = cached elif business: response_body = business.toJSON() if include_reviews: response_body['reviews'] = Review.reviews_for_reviewed(business.collection, business.id) response_body = json.dumps(response_body, cls=ComplexEncoder) redis_cache.set(cache_key, response_body) logger.debug('Retrieved business:{}'.format(business.id)) else: logger.debug('Failed to retrieve business:{}'.format(business_id)) request.response.status_int = 404 response_body = json.dumps({ 'status': 'error', 'message': 'failed to find business' }) request.response.body = response_body request.response.content_type = 'application/json' return request.response
def __init__(self, review): logger.debug('new review updated event') self.review = review
def keys(self, pattern): pattern_token = self._tokenize(pattern) keys = self._redis.keys(pattern_token) logger.debug('Got keys for pattern:%s' % pattern_token) return keys
def approve(): logger.info("==> approve()") headers = {'Content-Type': 'text/html'} params = {} if not request.form: logger.error("Missing post parameters. Bad request.") return make_response( render_template( 'error.html', error='Missing post form parameters. Bad request.'), 400, headers) # return "Bad Request", 400 else: query = REQUESTS.pop(request.form['reqid'], None) query = query.decode('UTF-8') print("The query: ", query) if not query: logger.error("Did not get a query string.") return make_response( render_template('error.html', error='Bad Request'), 400, headers) else: params = parse.parse_qs(query) print(params) if request.form["approve"]: logger.debug("Approving a token request.") if params["response_type"][0] == 'code': code = generate_auth_code() # Not sure whwer the user comes from. # user = request.form['user'] scope = params['scope'][0] req_scope = scope.split(' ') client = get_client_from_query(params) print(client) client_scope = client['scope'] same = [item for item in req_scope if item in client_scope] if len(same) == 0: # Client asked for a scope it could not have. return make_response( render_template('error.html', error='Invalid Scope'), 400, headers) # Save for later. A dictionary of stuff CODES[code] = { 'authorizationRequest': query, 'scope': scope, 'client_id': client['client_id'] } # Build the redirect url redirect_uri = params['redirect_uri'][0] if redirect_uri not in client['redirect_uris']: return make_response( render_template('error.html', error='Invalid redirect URI.'), 400, headers) state = params['state'][0] payload = {'code': code, 'state': state} the_location = ''.join( (redirect_uri, '?', parse.urlencode(payload))) print(the_location) return redirect(the_location, code=302) return "Got here", 200
from service.flask import create_app from service import logger application = create_app() logger.debug("Application initialized.")