def index(self): ''' Return information about the current logged in user. **Example Response** .. sourcecode:: json { "email": "*****@*****.**", "id": 201, "is_admin": false, "url": "https://quickpin/api/user/201" } :<header Content-Type: application/json :<header X-Auth: the client's auth token :>json str email: the current user's e-mail address :>json int id: user's unique identifier :>json bool is_admin: true if current user is an administrator :>json str url: API endpoint for data about this user :>header Content-Type: application/json :status 200: user is logged in :status 401: user is not logged in ''' return jsonify( email=g.user.email, id=g.user.id, is_admin=g.user.is_admin, url=url_for('UserView:get', id_=g.user.id) )
def index(self): ''' Return information about the current logged in user. **Example Response** .. sourcecode:: json { "email": "*****@*****.**", "id": 201, "is_admin": false, "thumb": "iVBORw0KGgoAAAANS...", "url": "https://quickpin/api/user/201" } :<header Content-Type: application/json :<header X-Auth: the client's auth token :>json str email: the current user's e-mail address :>json int id: user's unique identifier :>json bool is_admin: true if current user is an administrator :>json str thumb: PNG thumbnail for this user, base64 encoded :>json str url: API endpoint for data about this user :>header Content-Type: application/json :status 200: user is logged in :status 401: user is not logged in ''' return jsonify(email=g.user.email, id=g.user.id, is_admin=g.user.is_admin, thumb=g.user.thumb_data(), url=url_for('UserView:get', id_=g.user.id))
def index(self): ''' Return an array of all labels. **Example Response** .. sourcecode: json { "labels": [ { "id": 1, "name": "gender", "url": "https://quickpin/api/label/1", }, ... ], "total_count": 2 } :<header Content-Type: application/json :<header X-Auth: the client's auth token :query page: the page number to display (default: 1) :query rpp: the number of results per page (default: 10) :query site: name of site to filter by :>header Content-Type: application/json :>json list labels: a list of label objects :>json int labels[n].id: unique identifier for profile :>json str labels[n].name: the label name :>json str labels[n].url: URL endpoint for retriving more data about this label :status 200: ok :status 400: invalid argument[s] :status 401: authentication required ''' page, results_per_page = get_paging_arguments(request.args) query = g.db.query(Label) total_count = query.count() query = query.order_by(Label.name.asc()) \ .limit(results_per_page) \ .offset((page - 1) * results_per_page) labels = list() for label in query: data = label.as_dict() data['url'] = url_for('LabelView:get', id_=label.id) labels.append(data) return jsonify( labels=labels, total_count=total_count )
def index(self): """ Return an array of all labels. **Example Response** .. sourcecode:: json { "labels": [ { "id": 1, "name": "gender", "url": "https://quickpin/api/label/1", }, ... ], "total_count": 2 } :<header Content-Type: application/json :<header X-Auth: the client's auth token :query page: the page number to display (default: 1) :query rpp: the number of results per page (default: 10) :>header Content-Type: application/json :>json list labels: a list of label objects :>json int labels[n].id: unique identifier for profile :>json str labels[n].name: the label name :>json str labels[n].url: URL endpoint for retriving more data about this label :status 200: ok :status 400: invalid argument[s] :status 401: authentication required """ page, results_per_page = get_paging_arguments(request.args) query = g.db.query(Label) total_count = query.count() query = query.order_by(Label.name.asc()) \ .limit(results_per_page) \ .offset((page - 1) * results_per_page) labels = list() for label in query: data = label.as_dict() data['url'] = url_for('LabelView:get', id_=label.id) labels.append(data) return jsonify( labels=labels, total_count=total_count )
def index(self): ''' Provide links to sections of the API. **Example Response** .. sourcecode:: json { "authentication_url": "https://profiler/api/authentication/", "username_search": "https://profiler/api/username/" } :<header Content-Type: application/json :>header Content-Type: application/json :status 200: user is logged in ''' return jsonify(authentication_url=url_for('AuthenticationView:index'), search_url=url_for('UsernameView:index'))
def index(self): ''' Provide links to sections of the API. **Example Response** .. sourcecode:: json { "authentication_url": "https://quickpin/api/authentication/", "dark_user_url": "https://quickpin/api/search/" } :<header Content-Type: application/json :>header Content-Type: application/json :status 200: user is logged in ''' return jsonify(authentication_url=url_for('AuthenticationView:index'), search_url=url_for('SearchView:index'))
def get(self, id_): ''' Get the category identified by `id`. **Example Response** .. sourcecode: json { "id": 1, "name": "gender", "sites": [ { "id": 2, "name": "", "url": "". "status_code": "", "search_pattern": "", "category": "" }, ... ] } :<header Content-Type: application/json :<header X-Auth: the client's auth token :>header Content-Type: application/json :>json int id: unique identifier for category :>json str name: the category name :>json str url: URL url-for for retriving more data about this category :status 200: ok :status 400: invalid argument[s] :status 401: authentication required :status 404: category does not exist ''' # Get category. id_ = get_int_arg('id_', id_) category = g.db.query(Category).filter(Category.id == id_).first() if category is None: raise NotFound("Category '%s' does not exist." % id_) response = category.as_dict() response['url-for'] = url_for('CategoryView:get', id_=category.id) # Send response. return jsonify(**response)
def index(self): ''' Provide links to sections of the API. **Example Response** .. sourcecode:: json { "authentication_url": "https://quickpin/api/authentication/", "dark_user_url": "https://quickpin/api/search/" } :<header Content-Type: application/json :>header Content-Type: application/json :status 200: user is logged in ''' return jsonify( authentication_url=url_for('AuthenticationView:index'), search_url=url_for('SearchView:index') )
def get(self, id_): ''' Get the note identified by `id`. **Example Response** .. sourcecode:: json { "id": 1, "category": "user annotation", "body": "This is a user annotation.", "profile_id": "10", "created_at": "2015-12-14T16:23:18.101558", "url": "https://quickpin/api/note/2", } :<header Content-Type: application/json :<header X-Auth: the client's auth token :>header Content-Type: application/json :>json int id: unique identifier for note :>json str category: the user-defined category of this note :>json str body: the note :>json str profile_id: the unique id of the profile this note belongs to :>json str created_at: when the note was created as iso-formatted date string :>json str url: API endpoint URL for this note object :status 200: ok :status 400: invalid argument[s] :status 401: authentication required :status 404: note does not exist ''' # Get note. id_ = get_int_arg('id_', id_) note = g.db.query(ProfileNote).filter(ProfileNote.id == id_).first() if note is None: raise NotFound("Note '%s' does not exist." % id_) response = note.as_dict() response['url'] = url_for('ProfileNoteView:get', id_=note.id) # Send response. return jsonify(**response)
def get(self, id_): """ Get the label identified by `id`. **Example Response** .. sourcecode:: json { "id": 1, "name": "gender", "url": "https://quickpin/api/label/1", } :<header Content-Type: application/json :<header X-Auth: the client's auth token :>header Content-Type: application/json :>json int id: unique identifier for label :>json str name: the label name :>json str url: URL endpoint for retriving more data about this label :status 200: ok :status 400: invalid argument[s] :status 401: authentication required :status 404: label does not exist """ # Get label. id_ = get_int_arg('id_', id_) label = g.db.query(Label).filter(Label.id == id_).first() if label is None: raise NotFound("Label '%s' does not exist." % id_) response = label.as_dict() response['url'] = url_for('LabelView:get', id_=label.id) # Send response. return jsonify(**response)
def get(self, id_): ''' Get the label identified by `id`. **Example Response** .. sourcecode: json { "id": 1, "name": "gender", "url": "https://quickpin/api/label/1", } :<header Content-Type: application/json :<header X-Auth: the client's auth token :>header Content-Type: application/json :>json int id: unique identifier for label :>json str name: the label name :>json str url: URL endpoint for retriving more data about this label :status 200: ok :status 400: invalid argument[s] :status 401: authentication required :status 404: label does not exist ''' # Get label. id_ = get_int_arg('id_', id_) label = g.db.query(Label).filter(Label.id == id_).first() if label is None: raise NotFound("Label '%s' does not exist." % id_) response = label.as_dict() response['url'] = url_for('LabelView:get', id_=label.id) # Send response. return jsonify(**response)
def put(self, id_): ''' Update the category identified by `id`. **Example Request** ..sourcode:: json { { "name": "priority sites" "sites": [1,5] }, } **Example Response** ..sourcecode:: json { "id": 1, "name": "priority sites", "sites": [ { "category": "books", "id": 1, "name": "aNobil", "search_text": "- aNobii</title>", "status_code": 200, "url": "http://www.anobii.com/%s/books" }, { "category": "coding", "id": 5, "name": "bitbucket", "search_text": "\"username\":", "status_code": 200, "url": "https://bitbucket.org/api/2.0/users/%s" }, ... ] }, :<header Content-Type: application/json :<header X-Auth: the client's auth token :>json str name: the value of the name attribute :>header Content-Type: application/json :>json int id: unique identifier for category :>json str name: the category name :>json list sites: list of sites associated with this category :>json str sites[n].category: the site category :>json str sites[n].id: the unique id for site :>json str sites[n].name: the site name :>json str sites[n].search_text: string search pattern :>json str sites[n].status_code: server response code for site :>json str sites[n].url: the site url :status 200: updated :status 400: invalid request body :status 401: authentication required ''' editable_fields = ['name', 'sites'] # Get category. id_ = get_int_arg('id_', id_) category = g.db.query(Category).filter(Category.id == id_).first() if category is None: raise NotFound("Category '%s' does not exist." % id_) request_json = request.get_json() # Validate data and set attributes if request_json is None: raise BadRequest("Specify at least one editable field: {}".format( editable_fields)) for field in request_json: if field not in editable_fields: raise BadRequest( "'{}' is not one of the editable fields: {}".format( field, editable_fields)) if 'name' in request_json: validate_json_attr('name', GROUP_ATTRS, request_json) category.name = request_json['name'].strip() if 'sites' in request_json: try: request_site_ids = [int(s) for s in request_json['sites']] except ValueError: raise BadRequest('Sites must be a list of integer site ids') if len(request_site_ids) == 0: raise BadRequest('Categorys must have at least one site') sites = g.db.query(Site) \ .filter(Site.id.in_(request_site_ids)) \ .all() site_ids = [site.id for site in sites] missing_sites = list(set(request_site_ids) - set(site_ids)) if len(missing_sites) > 0: raise BadRequest('Site ids "{}" do not exist'.format( ','.join(missing_sites))) else: category.sites = sites # Save the updated category g.db.add(category) try: g.db.commit() except DBAPIError as e: g.db.rollback() raise BadRequest('Database error: {}'.format(e)) # Send redis notifications notify_mask_client(channel='category', message={ 'id': category.id, 'name': category.name, 'status': 'updated', 'resource': url_for('CategoryView:get', id_=category.id) }) response = category.as_dict() response['url-for'] = url_for('CategoryView:get', id_=category.id) # Send response. return jsonify(**response)
def index(self): ''' Return an array of all categories. **Example Response** .. sourcecode: json { "categories": [ { "id": 1, "name": "gender", "sites": [ { "category": "books", "id": 2, "name": "aNobil", "search_text": "- aNobii</title>", "status_code": 200, "url": "http://www.anobii.com/%s/books" }, ... ] }, ... ], "total_count": 2 } :<header Content-Type: application/json :<header X-Auth: the client's auth token :query page: the page number to display (default: 1) :query rpp: the number of results per page (default: 10) :>header Content-Type: application/json :>json list categories: a list of category objects :>json str categories[n].category: the category category :>json int categories[n].id: unique identifier for category :>json str categories[n].name: the category name :>json list categories[n].sites: list of sites associated with this category :>json str categories[n].sites[n].category: the site category :>json str categories[n].sites[n].id: the unique id for site :>json str categories[n].sites[n].name: the site name :>json str categories[n].sites[n].search_text: string search pattern :>json str categories[n].sites[n].status_code: server response code for site :>json str categories[n].sites[n].url: the site url :status 200: ok :status 400: invalid argument[s] :status 401: authentication required ''' page, results_per_page = get_paging_arguments(request.args) query = g.db.query(Category) total_count = query.count() query = query.order_by(Category.name.asc()) \ .limit(results_per_page) \ .offset((page - 1) * results_per_page) categories = list() for category in query: data = category.as_dict() data['url-for'] = url_for('CategoryView:get', id_=category.id) categories.append(data) return jsonify(categories=categories, total_count=total_count)
def put(self, id_): ''' Update the profile identified by `id` with submitted data. The following attribute are modifiable: * is_interesting * lables **Example Request** .. sourcecode:: json { "is_interesting": true, "labels": [ {"name": "male"}, {"name": "british"}, ... ], ... } **Example Response** .. sourcecode:: json { "avatar_url": "https://quickpin/api/file/1", "avatar_thumb_url": "https://quickpin/api/file/2", "description": "A human being.", "follower_count": 71, "friend_count": 28, "id": 1, "is_stub": false, "is_interesting": true, "join_date": "2012-01-30T15:11:35", "labels": [ { "id": 1, "name": "male" }, { "id": 2, "name": "british" }, ], "last_update": "2015-08-18T10:51:16", "location": "Washington, DC", "name": "John Doe", "post_count": 1666, "private": false, "site": "twitter", "site_name": "Twitter", "time_zone": "Central Time (US & Canada)", "upstream_id": "11009418", "url": "https://quickpin/api/profile/1", "username": "******", "usernames": [ { "end_date": "2012-06-30T15:00:00", "start_date": "2012-01-01T12:00:00", "username": "******" }, ... ] } :<header Content-Type: application/json :<header X-Auth: the client's auth token :>json bool is_interesting: whether profile is marked as interesting :>json list labels: whether profile is marked as interesting :>header Content-Type: application/json :>json str avatar_url: URL to the user's current avatar :>json str avatar_thumb_url: URL to a 32x32px thumbnail of the user's current avatar :>json str description: profile description :>json int follower_count: number of followers :>json int friend_count: number of friends (a.k.a. followees) :>json int id: unique identifier for profile :>json bool is_stub: indicates that this is a stub profile, e.g. related to another profile but has not been fully imported :>json bool is_interesting: indicates whether this profile has been marked as interesting. The value can be null. :>json str join_date: the date this profile joined its social network (ISO-8601) :>json list labels: list of labels for this profile :>json int label[n].id: the unique id for this label :>json str label[n].name: the label :>json str last_update: the last time that information about this profile was retrieved from the social media site (ISO-8601) :>json str location: geographic location provided by the user, as free text :>json str name: the full name provided by this user :>json int post_count: the number of posts made by this profile :>json bool private: true if this is a private account (i.e. not world- readable) :>json str site: machine-readable site name that this profile belongs to :>json str site_name: human-readable site name that this profile belongs to :>json str time_zone: the user's provided time zone as free text :>json str upstream_id: the user ID assigned by the social site :>json str url: URL endpoint for retriving more data about this profile :>json str username: the current username for this profile :>json list usernames: list of known usernames for this profile :>json str usernames[n].end_date: the last known date this username was used for this profile :>json str usernames[n].start_date: the first known date this username was used for this profile :>json str usernames[n].username: a username used for this profile :status 202: accepted for background processing :status 400: invalid request body :status 401: authentication required ''' redis = worker.get_redis() # Get profile. id_ = get_int_arg('id_', id_) current_avatar_id = self._current_avatar_subquery() profile, avatar = g.db.query(Profile, Avatar) \ .outerjoin(Avatar, Avatar.id == current_avatar_id) \ .filter(Profile.id == id_).first() if profile is None: raise NotFound("Profile '%s' does not exist." % id_) request_json = request.get_json() # Validate put data and set attributes # Only 'is_interesting' and 'labels' are modifiable if 'is_interesting' in request_json: if isinstance(request_json['is_interesting'], bool): profile.is_interesting = request_json['is_interesting'] elif request_json['is_interesting'] is None: profile.is_interesting = None else: raise BadRequest("Attribute 'is_interesting' is type boolean," " or can be set as null") # labels expects the string 'name' rather than id, to avoid the need to # create labels before adding them. if 'labels' in request_json: labels = [] if isinstance(request_json['labels'], list): for label_json in request_json['labels']: if 'name' in label_json: label = g.db.query(Label) \ .filter(Label.name==label_json['name']) \ .first() if label is None: try: label = Label( name=label_json['name'].lower().strip() ) g.db.add(label) g.db.flush() redis.publish( 'label', json.dumps(label.as_dict()) ) except IntegrityError: g.db.rollback() raise BadRequest('Label could not be saved') except AssertionError: g.db.rollback() raise BadRequest( '"{}" contains non-alphanumeric character' .format( label_json['name'] ) ) labels.append(label) else: raise BadRequest("Label 'name' is required") profile.labels = labels else: raise BadRequest("'labels' must be a list") response = profile.as_dict() response['url'] = url_for('ProfileView:get', id_=profile.id) # Save the profile try: g.db.commit() redis.publish('profile_update', json.dumps(profile.as_dict())) except DBAPIError as e: g.db.rollback() raise BadRequest('Profile could not be saved') # Create usernames list. usernames = list() for username in profile.usernames: if username.end_date is not None: end_date = username.end_date.isoformat() else: end_date = None if username.start_date is not None: start_date = username.start_date.isoformat() else: start_date = None usernames.append({ 'end_date': end_date, 'username': username.username, 'start_date': start_date, }) response['usernames'] = usernames # Create avatar attributes. if avatar is not None: response['avatar_url'] = url_for( 'FileView:get', id_=avatar.file.id ) response['avatar_thumb_url'] = url_for( 'FileView:get', id_=avatar.thumb_file.id ) else: response['avatar_url'] = url_for( 'static', filename='img/default_user.png' ) response['avatar_thumb_url'] = url_for( 'static', filename='img/default_user_thumb.png' ) # Send response. return jsonify(**response)
def index(self): ''' Return an array of data about profiles. Note that this only returns full profiles, not "stub" profiles. If user A in QuickPin has a friend/follower user B but user B is not in QuickPin, then a "stub" profile is created for user B. **Example Response** .. sourcecode:: json { "profiles": [ { "avatar_url": "https://quickpin/api/file/5", "avatar_thumb_url": "https://quickpin/api/file/6", "description": "A human being.", "follower_count": 12490, "friend_count": 294, "id": 5, "is_stub": False, "is_interesting": False, "join_date": "2010-01-30T18:21:35", "last_update": "2015-08-18T10:51:16", "location": "Washington, DC", "name": "John Q. Doe", "post_count": 230, "private": false, "site": "twitter", "time_zone": "Central Time (US & Canada)", "upstream_id": "123456", "url": "https://quickpin/api/profile/5", "username": "******" }, ... ], "total_count": 5 } :<header Content-Type: application/json :<header X-Auth: the client's auth token :query page: the page number to display (default: 1) :query rpp: the number of results per page (default: 10) :query site: name of site to filter by :>header Content-Type: application/json :>json list profiles: a list of profile objects :>json str profiles[n].avatar_url: a URL to the user's current avatar image :>json str profiles[n].avatar_thumb_url: a URL to a 32x32px thumbnail of the user's current avatar image :>json str profiles[n].description: profile description :>json int profiles[n].follower_count: number of followers :>json int profiles[n].friend_count: number of friends (a.k.a. followees) :>json int profiles[n].id: unique identifier for profile :>json bool profiles[n].is_stub: indicates that this is a stub profile, e.g. related to another profile but has not been fully imported (for this particular endpoint, is_stub will always be false) :>json bool is_interesting: indicates whether this profile has been tagged as interesting. The value can be null. :>json str profiles[n].join_date: the date this profile joined its social network (ISO-8601) :>json str profiles[n].last_update: the last time that information about this profile was retrieved from the social media site (ISO-8601) :>json str profiles[n].location: geographic location provided by the user, as free text :>json str profiles[n].name: the full name provided by this user :>json int profiles[n].post_count: the number of posts made by this profile :>json bool profiles[n].private: true if this is a private account (i.e. not world-readable) :>json str profiles[n].site: machine-readable site name that this profile belongs to :>json str profiles[n].site_name: human-readable site name that this profile belongs to :>json str profiles[n].time_zone: the user's provided time zone as free text :>json str profiles[n].upstream_id: the user ID assigned by the social site :>json str profiles[n].url: URL endpoint for retriving more data about this profile :>json str profiles[n].username: the current username for this profile :>json int total_count: count of all profile objects, not just those on the current page :status 200: ok :status 400: invalid argument[s] :status 401: authentication required ''' page, results_per_page = get_paging_arguments(request.args) current_avatar_id = self._current_avatar_subquery() query = g.db.query(Profile, Avatar) \ .outerjoin(Avatar, Avatar.id==current_avatar_id) \ .filter(Profile.is_stub == False) # Parse filter arguments site = request.args.get('site', None) is_interesting = request.args.get('interesting', None) labels = request.args.get('label', None) if site is not None: query = query.filter(Profile.site == site) if is_interesting is not None: if is_interesting == 'yes': query = query.filter(Profile.is_interesting == True) elif is_interesting == 'no': query = query.filter(Profile.is_interesting == False) elif is_interesting == 'unset': query = query.filter(Profile.is_interesting == None) if labels is not None: for label in labels.split(','): query = query.filter( Profile.labels.any(Label.name==label.lower()) ) total_count = query.count() query = query.order_by(Profile.last_update.desc()) \ .limit(results_per_page) \ .offset((page - 1) * results_per_page) profiles = list() for profile, avatar in query: data = profile.as_dict() data['url'] = url_for('ProfileView:get', id_=profile.id) if avatar is not None: data['avatar_url'] = url_for( 'FileView:get', id_=avatar.file.id ) data['avatar_thumb_url'] = url_for( 'FileView:get', id_=avatar.thumb_file.id ) else: data['avatar_url'] = url_for( 'static', filename='img/default_user.png' ) data['avatar_thumb_url'] = url_for( 'static', filename='img/default_user_thumb.png' ) profiles.append(data) return jsonify( profiles=profiles, total_count=total_count )
def index(self): ''' Return an array of all notes. **Example Response** .. sourcecode:: json { "notes": [ { "id": 1, "category": "user annotation", "body": "This is an interesting) profile.", "profile_id": 1, "created_at": "2015-12-15T10:41:55.792492", "url": "https://quickpin/api/note/1", }, ... ], "total_count": 1 } :<header Content-Type: application/json :<header X-Auth: the client's auth token :query page: the page number to display (default: 1) :query rpp: the number of results per page (default: 10) :query profile_id: profile id to filter by :>header Content-Type: application/json :>json list notes: list of profile note objects :>json int list[n].id: unique identifier for the note :>json str list[n].category: the user-defined category of this note :>json str list[n].body: the note :>json str list[n].profile_id: the unique id of the profile this note belongs to :>json str list[n].created_at: the iso-formatted creation time of the note :>json str list[n].url: API endpoint URL for this note object :status 200: ok :status 400: invalid argument[s] :status 401: authentication required ''' # Parse paging arguments page, results_per_page = get_paging_arguments(request.args) # Create base query query = g.db.query(ProfileNote) # Parse filter arguments profile_id = request.args.get('profile_id', None) if profile_id is not None: query = query.filter(ProfileNote.profile_id == profile_id) # Store the total result count before paging arguments limit result set total_count = query.count() # Apply paging arguments query = query.order_by(ProfileNote.category.asc()) \ .limit(results_per_page) \ .offset((page - 1) * results_per_page) # Add API endpoint URL for each note object notes = list() for note in query: data = note.as_dict() data['url'] = url_for('ProfileNoteView:get', id_=note.id) notes.append(data) return jsonify(notes=notes, total_count=total_count)
def put(self, id_): ''' Update the note identified by `id`. **Example Request** .. sourcecode:: json { { "category": "user annotation", "body": "This profile belongs to two interesting networks", "profile_id": "25 ", }, } **Example Response** .. sourcecode:: json { "id": "2", "category": "user annotation", "body": "This profile belongs to an interesting network", "profile_id": "25 ", "created_at": "2015-12-14T16:23:18.101558", "url": "https://quickpin/api/note/2", } :<header Content-Type: application/json :<header X-Auth: the client's auth token :>header Content-Type: application/json :>json int id: unique identifier for the note :>json str category: the user-defined category of this note :>json str body: the note :>json str profile_id: the unique id of the profile this note belongs to :>json str created_at: the iso-formatted creation time of the note :>json str url: API endpoint URL for this note object :status 202: created :status 400: invalid request body :status 401: authentication required ''' # Get note. id_ = get_int_arg('id_', id_) note = g.db.query(ProfileNote).filter(ProfileNote.id == id_).first() if note is None: raise NotFound("Note '%s' does not exist." % id_) redis = worker.get_redis() request_json = request.get_json() # Validate data and set attributes if 'category' in request_json: if request_json['category'].strip() != '': note.category = request_json['category'].lower().strip() if 'body' in request_json: if request_json['body'].strip() != '': note.body = request_json['body'].strip() else: raise BadRequest('Attribute "name" cannot be an empty string') # Save the updated note try: g.db.commit() except DBAPIError: g.db.rollback() raise BadRequest('Could not update note.') # Generate SSE redis.publish('profile_notes', json.dumps(note.as_dict())) response = note.as_dict() response['url'] = url_for('ProfileNoteView:get', id_=note.id) # Send response. return jsonify(**response)
def put(self, id_): ''' Update the profile identified by `id` with submitted data. The following attributes are modifiable: * is_interesting * labels * score **Example Request** .. sourcecode:: json { "is_interesting": true, "labels": [ {"name": "male"}, {"name": "british"}, ... ], "score": 2323.0, ... } **Example Response** .. sourcecode:: json { "avatar_url": "https://quickpin/api/file/1", "avatar_thumb_url": "https://quickpin/api/file/2", "description": "A human being.", "follower_count": 71, "friend_count": 28, "id": 1, "is_stub": false, "is_interesting": true, "join_date": "2012-01-30T15:11:35", "labels": [ { "id": 1, "name": "male" }, { "id": 2, "name": "british" }, ], "last_update": "2015-08-18T10:51:16", "location": "Washington, DC", "name": "John Doe", "post_count": 1666, "private": false, "score": "-2.0621606863", "site": "twitter", "site_name": "Twitter", "time_zone": "Central Time (US & Canada)", "upstream_id": "11009418", "url": "https://quickpin/api/profile/1", "username": "******", "usernames": [ { "end_date": "2012-06-30T15:00:00", "start_date": "2012-01-01T12:00:00", "username": "******" }, ... ] } :<header Content-Type: application/json :<header X-Auth: the client's auth token :<json bool is_interesting: whether profile is marked as interesting :<json list labels: list of profile labels :<json float score: profile score :>header Content-Type: application/json :>json str avatar_url: URL to the user's current avatar :>json str avatar_thumb_url: URL to a 32x32px thumbnail of the user's current avatar :>json str description: profile description :>json int follower_count: number of followers :>json int friend_count: number of friends (a.k.a. followees) :>json int id: unique identifier for profile :>json bool is_stub: indicates that this is a stub profile, e.g. related to another profile but has not been fully imported :>json bool is_interesting: indicates whether this profile has been marked as interesting. The value can be null. :>json str join_date: the date this profile joined its social network (ISO-8601) :>json list labels: list of labels for this profile :>json int label[n].id: the unique id for this label :>json str label[n].name: the label :>json str last_update: the last time that information about this profile was retrieved from the social media site (ISO-8601) :>json str location: geographic location provided by the user, as free text :>json str name: the full name provided by this user :>json int note[n].id: the unique id for this note :>json int note[n].category: the user-defined category of this note :>json int note[n].body: the user-defined text-body of this note :>json int post_count: the number of posts made by this profile :>json bool private: true if this is a private account (i.e. not world- readable) :>json str score: user-defined score for this profile. Can be null. :>json str site: machine-readable site name that this profile belongs to :>json str site_name: human-readable site name that this profile belongs to :>json str time_zone: the user's provided time zone as free text :>json str upstream_id: the user ID assigned by the social site :>json str url: URL endpoint for retriving more data about this profile :>json str username: the current username for this profile :>json list usernames: list of known usernames for this profile :>json str usernames[n].end_date: the last known date this username was used for this profile :>json str usernames[n].start_date: the first known date this username was used for this profile :>json str usernames[n].username: a username used for this profile :status 202: accepted for background processing :status 400: invalid request body :status 401: authentication required ''' redis = worker.get_redis() # Get profile. id_ = get_int_arg('id_', id_) profile, avatar = g.db.query(Profile, Avatar) \ .outerjoin(Profile.current_avatar) \ .filter(Profile.id == id_).first() if profile is None: raise NotFound("Profile '%s' does not exist." % id_) request_json = request.get_json() # Validate put data and set attributes # Only 'is_interesting', 'score', and 'labels' are modifiable if 'is_interesting' in request_json: if isinstance(request_json['is_interesting'], bool): profile.is_interesting = request_json['is_interesting'] elif request_json['is_interesting'] is None: profile.is_interesting = None else: raise BadRequest("'is_interesting' is a boolean (true or false.") if 'score' in request_json: if request_json['score'] is None: profile.score = None else: try: profile.score = float(request_json['score']) except: raise BadRequest("'score' must be a decimal number.") # labels expects the string 'name' rather than id, to avoid the need to # create labels before adding them. if 'labels' in request_json: labels = [] if isinstance(request_json['labels'], list): for label_json in request_json['labels']: if 'name' in label_json: label = g.db.query(Label) \ .filter(Label.name==label_json['name']) \ .first() if label is None: try: label = Label( name=label_json['name'].lower().strip() ) g.db.add(label) g.db.flush() redis.publish( 'label', json.dumps(label.as_dict()) ) except IntegrityError: g.db.rollback() raise BadRequest('Label could not be saved') except AssertionError: g.db.rollback() raise BadRequest( '"{}" contains non-alphanumeric character' .format( label_json['name'] ) ) labels.append(label) else: raise BadRequest("Label 'name' is required") profile.labels = labels else: raise BadRequest("'labels' must be a list") response = profile.as_dict() response['url'] = url_for('ProfileView:get', id_=profile.id) # Save the profile try: g.db.commit() redis.publish('profile', json.dumps(profile.as_dict())) except DBAPIError as e: g.db.rollback() raise BadRequest('Profile could not be saved') # Create usernames list. usernames = list() for username in profile.usernames: if username.end_date is not None: end_date = username.end_date.isoformat() else: end_date = None if username.start_date is not None: start_date = username.start_date.isoformat() else: start_date = None usernames.append({ 'end_date': end_date, 'username': username.username, 'start_date': start_date, }) response['usernames'] = usernames # Create avatar attributes. if avatar is not None: response['avatar_url'] = url_for( 'FileView:get', id_=avatar.file.id ) response['avatar_thumb_url'] = url_for( 'FileView:get', id_=avatar.thumb_file.id ) else: response['avatar_url'] = url_for( 'static', filename='img/default_user.png' ) response['avatar_thumb_url'] = url_for( 'static', filename='img/default_user_thumb.png' ) # Send response. return jsonify(**response)
def get_relations(self, id_, reltype): ''' Return an array of profiles that are related to the specified profile by `reltype`, either "friends" or "followers". **Example Response** .. sourcecode:: json { "relations": [ { "avatar_thumb_url": "https://quickpin/api/file/1", "id": 3, "url": "https://quickpin/api/profile/3", "username": "******" }, { "avatar_thumb_url": "https://quickpin/api/file/2", "id": 4, "url": "https://quickpin/api/profile/4", "username": "******" }, ... } :>header Content-Type: application/json :>json list relations: list of related profiles. :>json int relations[n].avatar_thumb_url: a URL to a thumbnail of the user's current avatar :>json int relations[n].id: Unique identifier for relation's profile. :>json str relations[n].url: The URL to fetch this relation's profile. :>json str relations[n].username: This relation's username. :>json int total_count: Total count of all related profiles, not just those on the current page. :<header Content-Type: application/json :<header X-Auth: the client's auth token :query page: the page number to display (default: 1) :query rpp: the number of results per page (default: 10) :>header Content-Type: application/json :>json object relations Array of related profiles. :>json int relations[n].avatar_thumb_url a URL to a thumbnail of the user's current avatar :>json int relations[n].id Unique identifier for relation's profile. :>json str relations[n].url The URL to fetch this relation's profile. :>json str relations[n].username This relation's username. :>json int total_count Total count of all related profiles, not just those on the current page. :status 200: ok :status 400: invalid argument[s] :status 401: authentication required :status 404: user does not exist ''' page, results_per_page = get_paging_arguments(request.args) profile = g.db.query(Profile).filter(Profile.id == id_).first() if profile is None: raise NotFound('No profile with id={}.'.format(id_)) if reltype == 'friends': join_cond = (profile_join_self.c.friend_id == Profile.id) filter_cond = (profile_join_self.c.follower_id == id_) elif reltype == 'followers': join_cond = (profile_join_self.c.follower_id == Profile.id) filter_cond = (profile_join_self.c.friend_id == id_) else: raise NotFound('Invalid relation type "{}".'.format(reltype)) relationship_query = \ g.db.query(Profile, Avatar) \ .outerjoin(Profile.current_avatar) \ .join(profile_join_self, join_cond) \ .filter(filter_cond) total_count = relationship_query.count() relationship_query = relationship_query \ .order_by(Profile.is_stub, Profile.username) \ .limit(results_per_page) \ .offset((page - 1) * results_per_page) relations = list() for relation, avatar in relationship_query: if avatar is not None: thumb_url = url_for( 'FileView:get', id_=avatar.thumb_file.id ) else: thumb_url = url_for( 'static', filename='img/default_user_thumb.png' ) relations.append({ 'avatar_thumb_url': thumb_url, 'id': relation.id, 'url': url_for('ProfileView:get', id_=relation.id), 'username': relation.username, }) return jsonify( site_name=profile.site_name(), relations=relations, total_count=total_count, username=profile.username )
def get(self, id_): ''' .. http:get:: /api/profile/(int:id_) Get the profile identified by `id`. **Example Response** .. sourcecode:: json { "avatar_url": "https://quickpin/api/file/1", "avatar_thumb_url": "https://quickpin/api/file/2", "description": "A human being.", "follower_count": 71, "friend_count": 28, "id": 1, "is_stub": false, "is_interesting": false, "join_date": "2012-01-30T15:11:35", "labels": [ { "id": 1, "name": "male" }, ], "last_update": "2015-08-18T10:51:16", "location": "Washington, DC", "name": "John Doe", "post_count": 1666, "private": false, "score": "-2.0621606863", "site": "twitter", "site_name": "Twitter", "time_zone": "Central Time (US & Canada)", "upstream_id": "11009418", "url": "https://quickpin/api/profile/1", "username": "******", "usernames": [ { "end_date": "2012-06-30T15:00:00", "start_date": "2012-01-01T12:00:00", "username": "******" }, ... ] } :<header Content-Type: application/json :<header X-Auth: the client's auth token :>header Content-Type: application/json :>json str avatar_url: URL to the user's current avatar :>json str avatar_thumb_url: URL to a 32x32px thumbnail of the user's current avatar :>json str description: profile description :>json int follower_count: number of followers :>json int friend_count: number of friends (a.k.a. followees) :>json int id: unique identifier for profile :>json bool is_stub: indicates that this is a stub profile, e.g. related to another profile but has not been fully imported :>json bool is_interesting: indicates whether this profile has been marked as interesting. The value can be null. :>json str join_date: the date this profile joined its social network (ISO-8601) :>json list labels: list of labels for this profile :>json int label[n].id: the unique id for this label :>json str label[n].name: the label :>json str last_update: the last time that information about this profile was retrieved from the social media site (ISO-8601) :>json str location: geographic location provided by the user, as free text :>json str name: the full name provided by this user :>json int note[n].body: the text body of of the note :>json str note[n].category: the category of the note. :>json str note[n].created_at: time at which the note was created. :>json int post_count: the number of posts made by this profile :>json bool private: true if this is a private account (i.e. not world- readable) :>json str score: user defined score for this profile :>json str site: machine-readable site name that this profile belongs to :>json str site_name: human-readable site name that this profile belongs to :>json str time_zone: the user's provided time zone as free text :>json str upstream_id: the user ID assigned by the social site :>json str url: URL endpoint for retriving more data about this profile :>json str username: the current username for this profile :>json list usernames: list of known usernames for this profile :>json str usernames[n].end_date: the last known date this username was used for this profile :>json str usernames[n].start_date: the first known date this username was used for this profile :>json str usernames[n].username: a username used for this profile :status 200: ok :status 400: invalid argument[s] :status 401: authentication required :status 404: user does not exist ''' # Get profile. id_ = get_int_arg('id_', id_) try: profile, avatar = g.db.query(Profile, Avatar) \ .outerjoin(Profile.current_avatar) \ .filter(Profile.id == id_).one() except NoResultFound: raise NotFound("Profile '%s' does not exist." % id_) response = profile.as_dict() response['url'] = url_for('ProfileView:get', id_=profile.id) # Create usernames list. usernames = list() for username in profile.usernames: if username.end_date is not None: end_date = username.end_date.isoformat() else: end_date = None if username.start_date is not None: start_date = username.start_date.isoformat() else: start_date = None usernames.append({ 'end_date': end_date, 'username': username.username, 'start_date': start_date, }) response['usernames'] = usernames # Create avatar attributes. if avatar is not None: response['avatar_url'] = url_for( 'FileView:get', id_=avatar.file.id ) response['avatar_thumb_url'] = url_for( 'FileView:get', id_=avatar.thumb_file.id ) else: response['avatar_url'] = url_for( 'static', filename='img/default_user.png' ) response['avatar_thumb_url'] = url_for( 'static', filename='img/default_user_thumb.png' ) # Send response. return jsonify(**response)
def get_posts(self, id_): ''' Return an array of posts by this profile. **Example Response** .. sourcecode:: json { "posts": [ { "content": "If your #Tor relay is stolen or you lose control of it, please report it so we can blacklist it: https://t.co/imVnrh1FbD @TorProject", "id": 4, "language": "en", "last_update": "2015-08-19T18:17:07", "location": [ null, null ], "upstream_created": "2014-11-07T16:24:05", "upstream_id": "530878388605423616" }, ... ], "username": "******" } :<header Content-Type: application/json :<header X-Auth: the client's auth token :query page: the page number to display (default: 1) :query rpp: the number of results per page (default: 10) :>header Content-Type: application/json :>json list posts: List of post objects. :>json str posts[n].content: Text content of the post. :>json int posts[n].id: Unique identifier for post. :>json str posts[n].language: Language of post, e.g. 'en'. :>json str posts[n].last_update: The date and time that this record was updated from the social media site. :>json str posts[n].location: 2-element array of longitude and latitude. :>json str posts[n].upstream_created: The date this was posted. :>json str posts[n].upstream_id: The unique identifier assigned by the social media site. :>json str username: Username of the requested profile :>json str site_name: Site name associated with the requested profile :>json int total_count: Total count of all posts by this profile, not just those displayed on this page :status 200: ok :status 400: invalid argument[s] :status 401: authentication required :status 404: user does not exist ''' page, results_per_page = get_paging_arguments(request.args) profile = g.db.query(Profile).filter(Profile.id == id_).first() if profile is None: raise NotFound('No profile exists for id={}.'.format(id_)) posts = list() post_query = g.db.query(Post) \ .filter(Post.author_id == id_) total_count = post_query.count() post_query = post_query.order_by(Post.upstream_created.desc()) \ .limit(results_per_page) \ .offset((page - 1) * results_per_page) for post in post_query: post_dict = { 'content': post.content, 'id': post.id, 'language': post.language, 'last_update': isodate(post.last_update), 'location': (post.longitude, post.latitude), 'upstream_created': isodate(post.upstream_created), 'upstream_id': post.upstream_id, } if len(post.attachments) > 0: attachment = post.attachments[0] post_dict['attachment'] = { 'mime': attachment.mime, 'name': attachment.name, 'url': url_for('FileView:get', id_=attachment.id) } posts.append(post_dict) return jsonify( posts=posts, site_name=profile.site_name(), total_count=total_count, username=profile.username )
def get_notes(self, id_): ''' Return an array of all notes for this profile. **Example Response** .. sourcecode:: json { "notes": [ { "id": 1, "category": "user annotation", "body": "This is an interesting) profile.", "created_at": "2015-12-15T10:41:55.792492", "url": "https://quickpin/api/note/1", } ... ], "total_count": 1 "username: "******", "sitename": twitter, } :<header Content-Type: application/json :<header X-Auth: the client's auth token :query page: the page number to display (default: 1) :query rpp: the number of results per page (default: 10) :>header Content-Type: application/json :>json list notes: list of profile note objects :>json int list[n].id: unique identifier for the note :>json str list[n].category: the user-defined category of this note :>json str list[n].body: the note :>json str list[n].created_at: the iso-formatted creation time of the note :>json str list[n].url: API endpoint URL for this note object :>json str total_count: the total number of notes for this profile :>json str username: the username of this profile :>json str sitename: the name of the social site the profile belongs to :status 200: ok :status 400: invalid argument[s] :status 401: authentication required ''' # Parse paging arguments page, results_per_page = get_paging_arguments(request.args) profile = g.db.query(Profile).filter(Profile.id == id_).first() if profile is None: raise NotFound('No profile exists for id={}.'.format(id_)) # Store the total result count before paging arguments limit result set total_count = len(profile.notes) # Add API endpoint URL for each note object notes = list() for note in profile.notes: data = note.as_dict() data['url'] = url_for('ProfileNoteView:get', id_=note.id) notes.append(data) # Apply paging arguments start = (page -1) * results_per_page end = start + results_per_page notes = notes[start:end] return jsonify( notes=notes, total_count=total_count, site_name=profile.site_name(), username=profile.username, )
def put(self, id_): ''' Update the label identified by `id`. **Example Request** ..sourcode:: json { {"name": "gender"}, } **Example Response** ..sourcecode:: json { "id": "2", "name": "gender", "url": "https://quickpin/api/label/1", } :<header Content-Type: application/json :<header X-Auth: the client's auth token :>json str name: the value of the name attribute :>header Content-Type: application/json :>json int id: unique identifier for label :>json str name: the label name :>json str url: URL endpoint for retriving more data about this label :status 202: created :status 400: invalid request body :status 401: authentication required ''' # Get label. id_ = get_int_arg('id_', id_) label = g.db.query(Label).filter(Label.id == id_).first() if label is None: raise NotFound("Label '%s' does not exist." % id_) request_json = request.get_json() # Validate data and set attributes if 'name' in request_json: if request_json['name'].strip() != '': label.name = request_json['name'].lower().strip() else: raise BadRequest('Attribute "name" cannot be an empty string') else: raise BadRequest('Attribue "name" is required') # Save the updated label try: g.db.commit() except DBAPIError as e: g.db.rollback() raise BadRequest('Database error: {}'.format(e)) response = label.as_dict() response['url'] = url_for('LabelView:get', id_=label.id) # Send response. return jsonify(**response)
def post(self): ''' Create a category. **Example Request** ..sourcode:: json { "categories": [ { "name": "gender", "sites": [1, 2, 7] }, ... ] } **Example Response** ..sourcecode:: json { "message": "2 new categories created." } :<header Content-Type: application/json :<header X-Auth: the client's auth token :>json list categories: a list of categories to create :>json str categories[n].name: name of category to create :>header Content-Type: application/json :>json str message: api response message :status 200: created :status 400: invalid request body :status 401: authentication required ''' request_json = request.get_json() categories = list() # Validate input for category_json in request_json['categories']: validate_request_json(category_json, GROUP_ATTRS) try: request_site_ids = [int(s) for s in category_json['sites']] except TypeError: raise BadRequest('Sites must be integer site ids') if len(request_site_ids) == 0: raise BadRequest('At least one site is required.') sites = g.db.query(Site)\ .filter(Site.id.in_(request_site_ids))\ .all() site_ids = [site.id for site in sites] missing_sites = list(set(request_site_ids) - set(site_ids)) if len(missing_sites) > 0: raise BadRequest('Site ids {} do not exist'.format(','.join( str(s) for s in missing_sites))) # Create categories for category_json in request_json['categories']: try: category = Category(name=category_json['name'].strip(), sites=sites) g.db.add(category) g.db.flush() # Create dict for API JSON response category_dict = category.as_dict() # Add a link to the created category category_dict['url-for'] = url_for('CategoryView:get', id_=category.id) categories.append(category_dict) except IntegrityError: g.db.rollback() raise BadRequest('Category "{}" already exists'.format( category.name)) # Save categories g.db.commit() # Send redis notifications for category in categories: notify_mask_client(channel='category', message={ 'id': category['id'], 'name': category['name'], 'status': 'created', 'resource': category['url-for'] }) message = '{} new categories created' \ .format(len(request_json['categories'])) response = jsonify(message=message, categories=categories) response.status_code = 200 return response
def get_posts(self, id_): ''' Return an array of posts by this profile. **Example Response** .. sourcecode:: json { "posts": [ { "content": "If your #Tor relay is stolen or you lose control of it, please report it so we can blacklist it: https://t.co/imVnrh1FbD @TorProject", "id": 4, "language": "en", "last_update": "2015-08-19T18:17:07", "location": [ null, null ], "upstream_created": "2014-11-07T16:24:05", "upstream_id": "530878388605423616" }, ... ], "username": "******" } :<header Content-Type: application/json :<header X-Auth: the client's auth token :query page: the page number to display (default: 1) :query rpp: the number of results per page (default: 10) :>header Content-Type: application/json :>json list posts Array of post objects. :>json str posts[n].content Text content of the post. :>json int posts[n].id Unique identifier for post. :>json str posts[n].language Language of post, e.g. 'en'. :>json str posts[n].last_update The date and time that this record was updated from the social media site. :>json str posts[n].location 2-element array of longitude and latitude. :>json str posts[n].upstream_created The date this was posted. :>json str posts[n].upstream_id The unique identifier assigned by the social media site. :>json str username Username of the requested profile :>json str site_name Site name associated with the requested profile :>json int total_count Total count of all posts by this profile, not just those displayed on this page :status 200: ok :status 400: invalid argument[s] :status 401: authentication required :status 404: user does not exist ''' page, results_per_page = get_paging_arguments(request.args) profile = g.db.query(Profile).filter(Profile.id == id_).first() if profile is None: raise NotFound('No profile exists for id={}.'.format(id_)) posts = list() post_query = g.db.query(Post) \ .filter(Post.author_id == id_) total_count = post_query.count() post_query = post_query.order_by(Post.upstream_created.desc()) \ .limit(results_per_page) \ .offset((page - 1) * results_per_page) for post in post_query: post_dict = { 'content': post.content, 'id': post.id, 'language': post.language, 'last_update': isodate(post.last_update), 'location': (post.longitude, post.latitude), 'upstream_created': isodate(post.upstream_created), 'upstream_id': post.upstream_id, } if len(post.attachments) > 0: attachment = post.attachments[0] post_dict['attachment'] = { 'mime': attachment.mime, 'name': attachment.name, 'url': url_for('FileView:get', id_=attachment.id) } posts.append(post_dict) return jsonify( posts=posts, site_name=profile.site_name(), total_count=total_count, username=profile.username )
def get(self, id_): ''' Get the profile identified by `id`. **Example Response** .. sourcecode:: json { "avatar_url": "https://quickpin/api/file/1", "avatar_thumb_url": "https://quickpin/api/file/2", "description": "A human being.", "follower_count": 71, "friend_count": 28, "id": 1, "is_stub": false, "is_interesting": false, "join_date": "2012-01-30T15:11:35", "labels": [ { "id": 1, "name": "male" }, ], "last_update": "2015-08-18T10:51:16", "location": "Washington, DC", "name": "John Doe", "post_count": 1666, "private": false, "site": "twitter", "site_name": "Twitter", "time_zone": "Central Time (US & Canada)", "upstream_id": "11009418", "url": "https://quickpin/api/profile/1", "username": "******", "usernames": [ { "end_date": "2012-06-30T15:00:00", "start_date": "2012-01-01T12:00:00", "username": "******" }, ... ] } :<header Content-Type: application/json :<header X-Auth: the client's auth token :>header Content-Type: application/json :>json str avatar_url: URL to the user's current avatar :>json str avatar_thumb_url: URL to a 32x32px thumbnail of the user's current avatar :>json str description: profile description :>json int follower_count: number of followers :>json int friend_count: number of friends (a.k.a. followees) :>json int id: unique identifier for profile :>json bool is_stub: indicates that this is a stub profile, e.g. related to another profile but has not been fully imported :>json bool is_interesting: indicates whether this profile has been marked as interesting. The value can be null. :>json str join_date: the date this profile joined its social network (ISO-8601) :>json list labels: list of labels for this profile :>json int label[n].id: the unique id for this label :>json str label[n].name: the label :>json str last_update: the last time that information about this profile was retrieved from the social media site (ISO-8601) :>json str location: geographic location provided by the user, as free text :>json str name: the full name provided by this user :>json int post_count: the number of posts made by this profile :>json bool private: true if this is a private account (i.e. not world- readable) :>json str site: machine-readable site name that this profile belongs to :>json str site_name: human-readable site name that this profile belongs to :>json str time_zone: the user's provided time zone as free text :>json str upstream_id: the user ID assigned by the social site :>json str url: URL endpoint for retriving more data about this profile :>json str username: the current username for this profile :>json list usernames: list of known usernames for this profile :>json str usernames[n].end_date: the last known date this username was used for this profile :>json str usernames[n].start_date: the first known date this username was used for this profile :>json str usernames[n].username: a username used for this profile :status 200: ok :status 400: invalid argument[s] :status 401: authentication required :status 404: user does not exist ''' # Get profile. id_ = get_int_arg('id_', id_) current_avatar_id = self._current_avatar_subquery() profile, avatar = g.db.query(Profile, Avatar) \ .outerjoin(Avatar, Avatar.id == current_avatar_id) \ .filter(Profile.id == id_).first() if profile is None: raise NotFound("Profile '%s' does not exist." % id_) response = profile.as_dict() response['url'] = url_for('ProfileView:get', id_=profile.id) # Create usernames list. usernames = list() for username in profile.usernames: if username.end_date is not None: end_date = username.end_date.isoformat() else: end_date = None if username.start_date is not None: start_date = username.start_date.isoformat() else: start_date = None usernames.append({ 'end_date': end_date, 'username': username.username, 'start_date': start_date, }) response['usernames'] = usernames # Create avatar attributes. if avatar is not None: response['avatar_url'] = url_for( 'FileView:get', id_=avatar.file.id ) response['avatar_thumb_url'] = url_for( 'FileView:get', id_=avatar.thumb_file.id ) else: response['avatar_url'] = url_for( 'static', filename='img/default_user.png' ) response['avatar_thumb_url'] = url_for( 'static', filename='img/default_user_thumb.png' ) # Send response. return jsonify(**response)
def get_relations(self, id_, reltype): ''' Return an array of profiles that are related to the specified profile by `reltype`, either "friends" or "followers". **Example Response** .. sourcecode:: json { "relations": [ { "avatar_thumb_url": "https://quickpin/api/file/1", "id": 3, "url": "https://quickpin/api/profile/3", "username": "******" }, { "avatar_thumb_url": "https://quickpin/api/file/2", "id": 4, "url": "https://quickpin/api/profile/4", "username": "******" }, ... } :<header Content-Type: application/json :<header X-Auth: the client's auth token :query page: the page number to display (default: 1) :query rpp: the number of results per page (default: 10) :>header Content-Type: application/json :>json object relations Array of related profiles. :>json int relations[n].avatar_thumb_url a URL to a thumbnail of the user's current avatar :>json int relations[n].id Unique identifier for relation's profile. :>json str relations[n].url The URL to fetch this relation's profile. :>json str relations[n].username This relation's username. :>json int total_count Total count of all related profiles, not just those on the current page. :status 200: ok :status 400: invalid argument[s] :status 401: authentication required :status 404: user does not exist ''' page, results_per_page = get_paging_arguments(request.args) current_avatar_id = self._current_avatar_subquery() profile = g.db.query(Profile).filter(Profile.id == id_).first() if profile is None: raise NotFound('No profile with id={}.'.format(id_)) if reltype == 'friends': join_cond = (profile_join_self.c.friend_id == Profile.id) filter_cond = (profile_join_self.c.follower_id == id_) elif reltype == 'followers': join_cond = (profile_join_self.c.follower_id == Profile.id) filter_cond = (profile_join_self.c.friend_id == id_) else: raise NotFound('Invalid relation type "{}".'.format(reltype)) relationship_query = \ g.db.query(Profile, Avatar) \ .outerjoin(Avatar, Avatar.id==current_avatar_id) \ .join(profile_join_self, join_cond) \ .filter(filter_cond) total_count = relationship_query.count() relationship_query = relationship_query \ .order_by(Profile.is_stub, Profile.username) \ .limit(results_per_page) \ .offset((page - 1) * results_per_page) relations = list() for relation, avatar in relationship_query: if avatar is not None: thumb_url = url_for( 'FileView:get', id_=avatar.thumb_file.id ) else: thumb_url = url_for( 'static', filename='img/default_user_thumb.png' ) relations.append({ 'avatar_thumb_url': thumb_url, 'id': relation.id, 'url': url_for('ProfileView:get', id_=relation.id), 'username': relation.username, }) return jsonify( site_name=profile.site_name(), relations=relations, total_count=total_count, username=profile.username )
def index(self): ''' Return an array of data about profiles. **Example Response** .. sourcecode:: json { "profiles": [ { "avatar_url": "https://quickpin/api/file/5", "avatar_thumb_url": "https://quickpin/api/file/6", "description": "A human being.", "follower_count": 12490, "friend_count": 294, "id": 5, "is_stub": False, "is_interesting": False, "join_date": "2010-01-30T18:21:35", "last_update": "2015-08-18T10:51:16", "location": "Washington, DC", "name": "John Q. Doe", "post_count": 230, "private": false, "score": "-2.0621606863", "site": "twitter", "time_zone": "Central Time (US & Canada)", "upstream_id": "123456", "url": "https://quickpin/api/profile/5", "username": "******" }, ... ], "total_count": 5 } :<header Content-Type: application/json :<header X-Auth: the client's auth token :query page: the page number to display (default: 1) :query rpp: the number of results per page (default: 10) :query interesting: filter by whether profile is set as interesting :query label: comma seperated list of labels to filter by :query site: name of site to filter by :query stub: filter by whether profile is stub :>header Content-Type: application/json :>json list profiles: a list of profile objects :>json str profiles[n].avatar_url: a URL to the user's current avatar image :>json str profiles[n].avatar_thumb_url: a URL to a 32x32px thumbnail of the user's current avatar image :>json str profiles[n].description: profile description :>json int profiles[n].follower_count: number of followers :>json int profiles[n].friend_count: number of friends (a.k.a. followees) :>json int profiles[n].id: unique identifier for profile :>json bool profiles[n].is_stub: indicates that this is a stub profile, e.g. related to another profile but has not been fully imported (for this particular endpoint, is_stub will always be false) :>json bool is_interesting: indicates whether this profile has been tagged as interesting. The value can be null. :>json str profiles[n].join_date: the date this profile joined its social network (ISO-8601) :>json str profiles[n].last_update: the last time that information about this profile was retrieved from the social media site (ISO-8601) :>json str profiles[n].location: geographic location provided by the user, as free text :>json str profiles[n].name: the full name provided by this user :>json int profiles[n].post_count: the number of posts made by this profile :>json bool profiles[n].private: true if this is a private account (i.e. not world-readable) :>json str profiles[n].score: user-defined score for this profile :>json str profiles[n].site: machine-readable site name that this profile belongs to :>json str profiles[n].site_name: human-readable site name that this profile belongs to :>json str profiles[n].time_zone: the user's provided time zone as free text :>json str profiles[n].upstream_id: the user ID assigned by the social site :>json str profiles[n].url: URL endpoint for retriving more data about this profile :>json str profiles[n].username: the current username for this profile :>json int total_count: count of all profile objects, not just those on the current page :status 200: ok :status 400: invalid argument[s] :status 401: authentication required ''' page, results_per_page = get_paging_arguments(request.args) allowed_sort_fields = { 'score': Profile.score, 'updated': Profile.last_update, 'added': Profile.id } sort_arguments = get_sort_arguments(request.args, '-added', allowed_sort_fields) query = g.db.query(Profile, Avatar) \ .outerjoin(Profile.current_avatar) # Parse filter arguments is_stub = request.args.get('stub', None) site = request.args.get('site', None) is_interesting = request.args.get('interesting', None) labels = request.args.get('label', None) if site is not None: query = query.filter(Profile.site == site) if is_stub is not None: if is_stub == '1': query = query.filter(Profile.is_stub == True) elif is_stub == '0': query = query.filter(Profile.is_stub == False) if is_interesting is not None: if is_interesting == 'yes': query = query.filter(Profile.is_interesting == True) elif is_interesting == 'no': query = query.filter(Profile.is_interesting == False) elif is_interesting == 'unset': query = query.filter(Profile.is_interesting == None) if labels is not None: for label in labels.split(','): query = query.filter( Profile.labels.any(Label.name==label.lower()) ) total_count = query.count() for argument in sort_arguments: query = query.order_by(argument) query = query.limit(results_per_page) \ .offset((page - 1) * results_per_page) profiles = list() for profile, avatar in query: data = profile.as_dict() data['url'] = url_for('ProfileView:get', id_=profile.id) if avatar is not None: data['avatar_url'] = url_for( 'FileView:get', id_=avatar.file.id ) data['avatar_thumb_url'] = url_for( 'FileView:get', id_=avatar.thumb_file.id ) else: data['avatar_url'] = url_for( 'static', filename='img/default_user.png' ) data['avatar_thumb_url'] = url_for( 'static', filename='img/default_user_thumb.png' ) profiles.append(data) return jsonify( profiles=profiles, total_count=total_count )
def put(self, id_): """ Update the label identified by `id`. **Example Request** .. sourcecode:: json { {"name": "gender"}, } **Example Response** .. sourcecode:: json { "id": "2", "name": "gender", "url": "https://quickpin/api/label/1", } :<header Content-Type: application/json :<header X-Auth: the client's auth token :>json str name: the value of the name attribute :>header Content-Type: application/json :>json int id: unique identifier for label :>json str name: the label name :>json str url: URL endpoint for retriving more data about this label :status 202: created :status 400: invalid request body :status 401: authentication required """ # Get label. id_ = get_int_arg('id_', id_) label = g.db.query(Label).filter(Label.id == id_).first() if label is None: raise NotFound("Label '%s' does not exist." % id_) request_json = request.get_json() # Validate data and set attributes if 'name' in request_json: if request_json['name'].strip() != '': label.name = request_json['name'].lower().strip() else: raise BadRequest('Attribute "name" cannot be an empty string') else: raise BadRequest('Attribue "name" is required') # Save the updated label try: g.db.commit() except DBAPIError as e: g.db.rollback() raise BadRequest('Database error: {}'.format(e)) response = label.as_dict() response['url'] = url_for('LabelView:get', id_=label.id) # Send response. return jsonify(**response)