示例#1
0
    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)
        )
示例#2
0
    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))
示例#3
0
    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
        )
示例#4
0
    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
        )
示例#5
0
    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'))
示例#6
0
    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'))
示例#7
0
    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)
示例#8
0
    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')
        )
示例#9
0
文件: note.py 项目: zanachka/quickpin
    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)
示例#10
0
    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)
示例#11
0
    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)
示例#12
0
    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)
示例#13
0
    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)
示例#14
0
    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)
示例#15
0
    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
        )
示例#16
0
文件: note.py 项目: zanachka/quickpin
    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)
示例#17
0
文件: note.py 项目: zanachka/quickpin
    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)
示例#18
0
    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)
示例#19
0
    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
        )
示例#20
0
    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)
示例#21
0
    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
        )
示例#22
0
    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,
        )
示例#23
0
    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)
示例#24
0
    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
示例#25
0
    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
        )
示例#26
0
    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)
示例#27
0
    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
        )
示例#28
0
    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
        )
示例#29
0
    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)