Blob Blame Raw
# -*- coding: utf-8 -*-

"""
 (c) 2015-2016 - Copyright Red Hat Inc

 Authors:
   Pierre-Yves Chibon <pingou@pingoured.fr>

"""

import collections
import datetime
from math import ceil

import arrow
import flask

import pagure
import pagure.exceptions
import pagure.lib
from pagure.api import API, api_method, APIERROR


def _get_user(username):
    """ Check user is valid or not
    """
    try:
        return pagure.lib.get_user(flask.g.session, username)
    except pagure.exceptions.PagureException:
        raise pagure.exceptions.APIError(404, error_code=APIERROR.ENOUSER)


@API.route('/user/<username>')
@api_method
def api_view_user(username):
    """
    User information
    ----------------
    Use this endpoint to retrieve information about a specific user.

    ::

        GET /api/0/user/<username>

    ::

        GET /api/0/user/ralph

    Sample response
    ^^^^^^^^^^^^^^^

    ::

        {
          "forks": [],
          "repos": [
            {
              "custom_keys": [],
              "description": "",
              "parent": null,
              "settings": {
                "issues_default_to_private": false,
                "Minimum_score_to_merge_pull-request": -1,
                "Web-hooks": None,
                "fedmsg_notifications": true,
                "always_merge": false,
                "project_documentation": true,
                "Enforce_signed-off_commits_in_pull-request": false,
                "pull_requests": true,
                "Only_assignee_can_merge_pull-request": false,
                "issue_tracker": true
              },
              "tags": [],
              "namespace": None,
              "priorities": {},
              "close_status": [
                "Invalid",
                "Insufficient data",
                "Fixed",
                "Duplicated"
              ],
              "milestones": {},
              "user": {
                "fullname": "ralph",
                "name": "ralph"
              },
              "date_created": "1426595173",
              "id": 5,
              "name": "pagure"
            }
          ],
          "user": {
            "fullname": "ralph",
            "name": "ralph"
          }
        }

    """
    httpcode = 200
    output = {}

    user = _get_user(username=username)

    repopage = flask.request.args.get('repopage', 1)
    try:
        repopage = int(repopage)
    except ValueError:
        repopage = 1

    forkpage = flask.request.args.get('forkpage', 1)
    try:
        forkpage = int(forkpage)
    except ValueError:
        forkpage = 1

    repos = pagure.lib.search_projects(
        flask.g.session,
        username=username,
        fork=False)

    forks = pagure.lib.search_projects(
        flask.g.session,
        username=username,
        fork=True)

    output['user'] = user.to_json(public=True)
    output['repos'] = [repo.to_json(public=True) for repo in repos]
    output['forks'] = [repo.to_json(public=True) for repo in forks]

    jsonout = flask.jsonify(output)
    jsonout.status_code = httpcode
    return jsonout


@API.route('/user/<username>/issues')
@api_method
def api_view_user_issues(username):
    """
    List user's issues
    ---------------------
    List issues opened by or assigned to a specific user across all projects.

    ::

        GET /api/0/user/<username>/issues

    Parameters
    ^^^^^^^^^^

    +---------------+---------+--------------+---------------------------+
    | Key           | Type    | Optionality  | Description               |
    +===============+=========+==============+===========================+
    | ``page``      | integer | Mandatory    | | The page requested.     |
    |               |         |              |   Defaults to 1.          |
    +---------------+---------+--------------+---------------------------+
    | ``status``    | string  | Optional     | | Filters the status of   |
    |               |         |              |   issues. Fetches all the |
    |               |         |              |   issues if status is     |
    |               |         |              |   ``all``. Default:       |
    |               |         |              |   ``Open``                |
    +---------------+---------+--------------+---------------------------+
    | ``tags``      | string  | Optional     | | A list of tags you      |
    |               |         |              |   wish to filter. If      |
    |               |         |              |   you want to filter      |
    |               |         |              |   for issues not having   |
    |               |         |              |   a tag, add an           |
    |               |         |              |   exclamation mark in     |
    |               |         |              |   front of it             |
    +---------------+---------+--------------+---------------------------+
    | ``milestones``| list of | Optional     | | Filter the issues       |
    |               | strings |              |   by milestone            |
    +---------------+---------+--------------+---------------------------+
    | ``no_stones`` | boolean | Optional     | | If true returns only the|
    |               |         |              |   issues having no        |
    |               |         |              |   milestone, if false     |
    |               |         |              |   returns only the issues |
    |               |         |              |   having a milestone      |
    +---------------+---------+--------------+---------------------------+
    | ``since``     | string  | Optional     | | Filter the issues       |
    |               |         |              |   updated after this date.|
    |               |         |              |   The date can either be  |
    |               |         |              |   provided as an unix date|
    |               |         |              |   or in the format Y-M-D  |
    +---------------+---------+--------------+---------------------------+
    | ``order``     | string  | Optional     | | Set the ordering of the |
    |               |         |              |   issues. This can be     |
    |               |         |              |   ``asc`` or ``desc``.    |
    |               |         |              |   Default: ``desc``       |
    +---------------+---------+--------------+---------------------------+
    | ``order_key`` | string  | Optional     | | Set the ordering key.   |
    |               |         |              |   This can be ``assignee``|
    |               |         |              |   , ``last_updated`` or   |
    |               |         |              |   name of other column.   |
    |               |         |              |   Default:                |
    |               |         |              |          ``date_created`` |
    +---------------+---------+--------------+---------------------------+
    | ``assignee``  | boolean | Optional     | | A boolean of whether to |
    |               |         |              |   return the issues       |
    |               |         |              |   assigned to this user   |
    |               |         |              |   or not. Defaults to True|
    +---------------+---------+--------------+---------------------------+
    | ``author``    | boolean | Optional     | | A boolean of whether to |
    |               |         |              |   return the issues       |
    |               |         |              |   created by this user or |
    |               |         |              |   not. Defaults to True   |
    +---------------+---------+--------------+---------------------------+

    Sample response
    ^^^^^^^^^^^^^^^

    ::

        {
          "args": {
            "milestones": [],
            "no_stones": null,
            "order": null,
            "order_key": null,
            "since": null,
            "status": null,
            "tags": []
          },
          "issues_assigned": [
            {
              "assignee": {
                "fullname": "Anar Adilova",
                "name": "anar"
              },
              "blocks": [],
              "close_status": null,
              "closed_at": null,
              "comments": [],
              "content": "Test Issue",
              "custom_fields": [],
              "date_created": "1510124763",
              "depends": [],
              "id": 2,
              "last_updated": "1510124763",
              "milestone": null,
              "priority": null,
              "private": false,
              "status": "Open",
              "tags": [],
              "title": "issue4",
              "user": {
                "fullname": "Anar Adilova",
                "name": "anar"
              }
            }
          ],
          "issues_created": [
            {
              "assignee": {
                "fullname": "Anar Adilova",
                "name": "anar"
              },
              "blocks": [],
              "close_status": null,
              "closed_at": null,
              "comments": [],
              "content": "Test Issue",
              "custom_fields": [],
              "date_created": "1510124763",
              "depends": [],
              "id": 2,
              "last_updated": "1510124763",
              "milestone": null,
              "priority": null,
              "private": false,
              "status": "Open",
              "tags": [],
              "title": "issue4",
              "user": {
                "fullname": "Anar Adilova",
                "name": "anar"
              }
            }
          ],
          "total_issues_assigned": 1,
          "total_issues_created": 1
        }


    """
    milestone = flask.request.args.getlist('milestones', None)
    no_stones = flask.request.args.get('no_stones', None)
    if no_stones is not None:
        if str(no_stones).lower() in ['1', 'true', 't']:
            no_stones = True
        else:
            no_stones = False
    since = flask.request.args.get('since', None)
    order = flask.request.args.get('order', None)
    order_key = flask.request.args.get('order_key', None)
    status = flask.request.args.get('status', None)
    tags = flask.request.args.getlist('tags')
    tags = [tag.strip() for tag in tags if tag.strip()]
    page = flask.request.args.get('page', 1)

    assignee = flask.request.args.get('assignee', '').lower()\
        not in ['false', '0', 'f']
    author = flask.request.args.get('author', '').lower() \
        not in ['false', '0', 'f']

    try:
        page = int(page)
        if page <= 0:
            raise ValueError()
    except ValueError:
        raise pagure.exceptions.APIError(
            400, error_code=APIERROR.ENOCODE,
            error='Invalid page requested')

    offset = (page - 1) * 50
    limit = page * 50

    params = {
        'session': flask.g.session,
        'tags': tags,
        'milestones': milestone,
        'order': order,
        'order_key': order_key,
        'no_milestones': no_stones,
        'offset': offset,
        'limit': limit,
    }

    if status is not None:
        if status.lower() == 'all':
            params.update({'status': None})
        elif status.lower() == 'closed':
            params.update({'closed': True})
        else:
            params.update({'status': status})
    else:
        params.update({'status': 'Open'})

    updated_after = None
    if since:
        # Validate and convert the time
        if since.isdigit():
            # We assume its a timestamp, so convert it to datetime
            try:
                updated_after = datetime.datetime.fromtimestamp(int(since))
            except ValueError:
                raise pagure.exceptions.APIError(
                    400, error_code=APIERROR.ETIMESTAMP)
        else:
            # We assume datetime format, so validate it
            try:
                updated_after = datetime.datetime.strptime(since, '%Y-%m-%d')
            except ValueError:
                raise pagure.exceptions.APIError(
                    400, error_code=APIERROR.EDATETIME)

    params.update({'updated_after': updated_after})

    issues_created = []
    issues_created_pages = 1
    if author:
        # Issues authored by this user
        params_created = params.copy()
        params_created.update({"author": username})
        issues_created = pagure.lib.search_issues(**params_created)
        params_created.update({"offset": None, 'limit': None, 'count': True})
        issues_created_cnt = pagure.lib.search_issues(**params_created)
        issues_created_pages = int(
            ceil(issues_created_cnt / float(50))) or 1

    issues_assigned = []
    issues_assigned_pages = 1
    if assignee:
        # Issues assigned to this user
        params_assigned = params.copy()
        params_assigned.update({"assignee": username})
        issues_assigned = pagure.lib.search_issues(**params_assigned)
        params_assigned.update({"offset": None, 'limit': None, 'count': True})
        issues_assigned_cnt = pagure.lib.search_issues(**params_assigned)
        issues_assigned_pages = int(
            ceil(issues_assigned_cnt / float(50))) or 1

    jsonout = flask.jsonify({
        'total_issues_created_pages': issues_created_pages,
        'total_issues_assigned_pages': issues_assigned_pages,
        'total_issues_created': len(issues_created),
        'total_issues_assigned': len(issues_assigned),
        'issues_created': [issue.to_json(public=True, with_project=True)
                           for issue in issues_created],
        'issues_assigned': [issue.to_json(public=True, with_project=True)
                            for issue in issues_assigned],
        'args': {
            'milestones': milestone,
            'no_stones': no_stones,
            'order': order,
            'order_key': order_key,
            'since': since,
            'status': status,
            'tags': tags,
            'page': page,
            'assignee': assignee,
            'author': author,
        }
    })
    return jsonout


@API.route('/user/<username>/activity/stats')
@api_method
def api_view_user_activity_stats(username):
    """
    User activity stats
    -------------------
    Use this endpoint to retrieve activity stats about a specific user over
    the last year.

    ::

        GET /api/0/user/<username>/activity/stats

    ::

        GET /api/0/user/ralph/activity/stats

        GET /api/0/user/ralph/activity/stats?format=timestamp

    Parameters
    ^^^^^^^^^^

    +---------------+----------+--------------+----------------------------+
    | Key           | Type     | Optionality  | Description                |
    +===============+==========+==============+============================+
    | ``username``  | string   | Mandatory    | | The username of the user |
    |               |          |              |   whose activity you are   |
    |               |          |              |   interested in.           |
    +---------------+----------+--------------+----------------------------+
    | ``format``    | string   | Optional     | | Allows changing the      |
    |               |          |              |   of the date/time returned|
    |               |          |              |   from iso format to unix  |
    |               |          |              |   timestamp                |
    |               |          |              |   Can be: `timestamp`      |
    |               |          |              |   or `isoformat`           |
    +---------------+----------+--------------+----------------------------+


    Sample response
    ^^^^^^^^^^^^^^^

    ::

        {
          "2015-11-04": 9,
          "2015-11-06": 3,
          "2015-11-09": 6,
          "2015-11-13": 4,
          "2015-11-15": 3,
          "2015-11-18": 15,
          "2015-11-19": 3,
          "2015-11-20": 15,
          "2015-11-26": 18,
          "2015-11-30": 116,
          "2015-12-02": 12,
          "2015-12-03": 2
        }

    or::

        {
          "1446591600": 9,
          "1446764400": 3,
          "1447023600": 6,
          "1447369200": 4,
          "1447542000": 3,
          "1447801200": 15,
          "1447887600": 3,
          "1447974000": 15,
          "1448492400": 18,
          "1448838000": 116,
          "1449010800": 12,
          "1449097200": 2
        }

    """
    date_format = flask.request.args.get('format', 'isoformat')

    user = _get_user(username=username)

    stats = pagure.lib.get_yearly_stats_user(
        flask.g.session,
        user,
        datetime.datetime.utcnow().date() + datetime.timedelta(days=1)
    )

    def format_date(d):
        if date_format == 'timestamp':
            d = d.strftime('%s')
        else:
            d = d.isoformat()
        return d

    stats = {format_date(d[0]): d[1] for d in stats}

    jsonout = flask.jsonify(stats)
    return jsonout


@API.route('/user/<username>/activity/<date>')
@api_method
def api_view_user_activity_date(username, date):
    """
    User activity on a specific date
    --------------------------------
    Use this endpoint to retrieve activity information about a specific user
    on the specified date.

    ::

        GET /api/0/user/<username>/activity/<date>

    ::

        GET /api/0/user/ralph/activity/2016-01-02

        GET /api/0/user/ralph/activity/2016-01-02?grouped=true


    Parameters
    ^^^^^^^^^^

    +---------------+----------+--------------+----------------------------+
    | Key           | Type     | Optionality  | Description                |
    +===============+==========+==============+============================+
    | ``username``  | string   | Mandatory    | | The username of the user |
    |               |          |              |   whose activity you are   |
    |               |          |              |   interested in.           |
    +---------------+----------+--------------+----------------------------+
    | ``date``      | string   | Mandatory    | | The date of interest,    |
    |               |          |              |   best provided in ISO     |
    |               |          |              |   format: YYYY-MM-DD       |
    +---------------+----------+--------------+----------------------------+
    | ``grouped``   | boolean  | Optional     | | Whether or not to group  |
    |               |          |              |   the commits              |
    +---------------+----------+--------------+----------------------------+


    Sample response
    ^^^^^^^^^^^^^^^

    ::

        {
          "activities": [
            {
              "date": "2016-02-24",
              "date_created": "1456305852",
              "description": "pingou created PR test#44",
              "description_mk": "<p>pingou created PR <a href=\"/test/pull-request/44\" title=\"Update test_foo\">test#44</a></p>",
              "id": 4067,
              "user": {
                "fullname": "Pierre-YvesC",
                "name": "pingou"
              }
            },
            {
              "date": "2016-02-24",
              "date_created": "1456305887",
              "description": "pingou commented on PR test#44",
              "description_mk": "<p>pingou commented on PR <a href=\"/test/pull-request/44\" title=\"Update test_foo\">test#44</a></p>",
              "id": 4112,
              "user": {
                "fullname": "Pierre-YvesC",
                "name": "pingou"
              }
            }
          ]
        }

    """  # noqa
    grouped = str(flask.request.args.get('grouped')).lower() in ['1', 'true']

    try:
        date = arrow.get(date)
        date = date.strftime('%Y-%m-%d')
    except arrow.parser.ParserError as err:
        raise pagure.exceptions.APIError(
            400, error_code=APIERROR.ENOCODE, error=str(err))

    user = _get_user(username=username)

    activities = pagure.lib.get_user_activity_day(flask.g.session, user, date)
    js_act = []
    if grouped:
        commits = collections.defaultdict(list)
        acts = []
        for activity in activities:
            if activity.log_type == 'committed':
                commits[activity.project.fullname].append(activity)
            else:
                acts.append(activity)
        for project in commits:
            if len(commits[project]) == 1:
                tmp = dict(
                    description_mk=pagure.lib.text2markdown(
                        str(commits[project][0]))
                )
            else:
                tmp = dict(
                    description_mk=pagure.lib.text2markdown(
                        '@%s pushed %s commits to %s' % (
                            username, len(commits[project]), project
                        )
                    )
                )
            js_act.append(tmp)
        activities = acts

    for act in activities:
        activity = act.to_json(public=True)
        activity['description_mk'] = pagure.lib.text2markdown(str(act))
        js_act.append(activity)

    jsonout = flask.jsonify(
        dict(
            activities=js_act,
            date=date,
        )
    )
    return jsonout


@API.route('/user/<username>/requests/filed')
@api_method
def api_view_user_requests_filed(username):
    """
    List pull-requests filled by user
    ---------------------------------
    Use this endpoint to retrieve a list of open pull requests a user has
    filed over the entire pagure instance.

    ::

        GET /api/0/user/<username>/requests/filed

    ::

        GET /api/0/user/dudemcpants/requests/filed

    Parameters
    ^^^^^^^^^^

    +---------------+----------+--------------+----------------------------+
    | Key           | Type     | Optionality  | Description                |
    +===============+==========+==============+============================+
    | ``username``  | string   | Mandatory    | | The username of the user |
    |               |          |              |   whose activity you are   |
    |               |          |              |   interested in.           |
    +---------------+----------+--------------+----------------------------+
    | ``page``      | integer  | Mandatory    | | The page requested.      |
    |               |          |              |   Defaults to 1.           |
    +---------------+----------+--------------+----------------------------+
    | ``status``    | string   | Optional     | | Filter the status of     |
    |               |          |              |   pull requests. Default:  |
    |               |          |              |   ``Open`` (open pull      |
    |               |          |              |   requests), can be        |
    |               |          |              |   ``Closed`` for closed    |
    |               |          |              |   requests, ``Merged``     |
    |               |          |              |   for merged requests, or  |
    |               |          |              |   ``Open`` for open        |
    |               |          |              |   requests.                |
    |               |          |              |   ``All`` returns closed,  |
    |               |          |              |   merged and open requests.|
    +---------------+----------+--------------+----------------------------+


    Sample response
    ^^^^^^^^^^^^^^^

    ::

        {
          "args": {
            "status": "open",
            "username": "dudemcpants",
            "page": 1,
          },
          "requests": [
            {
              "assignee": null,
              "branch": "master",
              "branch_from": "master",
              "closed_at": null,
              "closed_by": null,
              "comments": [],
              "commit_start": "3973fae98fc485783ca14f5c3612d85832185065",
              "commit_stop": "3973fae98fc485783ca14f5c3612d85832185065",
              "date_created": "1510227832",
              "id": 2,
              "initial_comment": null,
              "last_updated": "1510227833",
              "project": {
                  "access_groups": {
                    "admin": [],
                    "commit": [],
                    "ticket": []
                  },
                  "access_users": {
                    "admin": [],
                    "commit": [],
                    "owner": [
                      "ryanlerch"
                    ],
                    "ticket": []
                  },
                  "close_status": [],
                  "custom_keys": [],
                  "date_created": "1510227638",
                  "date_modified": "1510227638",
                  "description": "this is a quick project",
                  "fullname": "aquickproject",
                  "id": 1,
                  "milestones": {},
                  "name": "aquickproject",
                  "namespace": null,
                  "parent": null,
                  "priorities": {},
                  "tags": [],
                  "url_path": "aquickproject",
                  "user": {
                    "fullname": "ryanlerch",
                    "name": "ryanlerch"
                  }
              },
              "remote_git": null,
              "repo_from": {
                  "access_groups": {
                    "admin": [],
                    "commit": [],
                    "ticket": []
                  },
                  "access_users": {
                    "admin": [],
                    "commit": [],
                    "owner": [
                      "dudemcpants"
                    ],
                    "ticket": []
                  },
                  "close_status": [],
                  "custom_keys": [],
                  "date_created": "1510227729",
                  "date_modified": "1510227729",
                  "description": "this is a quick project",
                  "fullname": "forks/dudemcpants/aquickproject",
                  "id": 2,
                  "milestones": {},
                  "name": "aquickproject",
                  "namespace": null,
                  "parent": {
                    "access_groups": {
                      "admin": [],
                      "commit": [],
                      "ticket": []
                    },
                    "access_users": {
                      "admin": [],
                      "commit": [],
                      "owner": [
                        "ryanlerch"
                      ],
                      "ticket": []
                    },
                    "close_status": [],
                    "custom_keys": [],
                    "date_created": "1510227638",
                    "date_modified": "1510227638",
                    "description": "this is a quick project",
                    "fullname": "aquickproject",
                    "id": 1,
                    "milestones": {},
                    "name": "aquickproject",
                    "namespace": null,
                    "parent": null,
                    "priorities": {},
                    "tags": [],
                    "url_path": "aquickproject",
                    "user": {
                        "fullname": "ryanlerch",
                        "name": "ryanlerch"
                    }
                  },
                  "priorities": {},
                  "tags": [],
                  "url_path": "fork/dudemcpants/aquickproject",
                  "user": {
                    "fullname": "Dude McPants",
                    "name": "dudemcpants"
                  }
              },
              "status": "Open",
              "title": "Update README.md",
              "uid": "819e0b1c449e414fa291c914f28d73ec",
              "updated_on": "1510227832",
              "user": {
                "fullname": "Dude McPants",
                "name": "dudemcpants"
              }
            }
          ],
          "total_requests": 1
        }

    """
    status = flask.request.args.get('status', 'open')
    page = flask.request.args.get('page', 1)

    try:
        page = int(page)
        if page <= 0:
            raise ValueError()
    except ValueError:
        raise pagure.exceptions.APIError(
            400, error_code=APIERROR.ENOCODE,
            error='Invalid page requested')

    offset = (page - 1) * 50
    limit = page * 50

    orig_status = status
    if status.lower() == 'all':
        status = None
    else:
        status = status.capitalize()

    pullrequests = pagure.lib.get_pull_request_of_user(
        flask.g.session,
        username=username,
        status=status,
        offset=offset,
        limit=limit,
    )

    pullrequestslist = [
        pr.to_json(public=True, api=True)
        for pr in pullrequests
        if pr.user.username == username
    ]

    return flask.jsonify({
        'total_requests': len(pullrequestslist),
        'requests': pullrequestslist,
        'args': {
            'username': username,
            'status': orig_status,
            'page': page,
        }
    })


@API.route('/user/<username>/requests/actionable')
@api_method
def api_view_user_requests_actionable(username):
    """
    List PRs actionable by user
    ---------------------------

    Use this endpoint to retrieve a list of open pull requests a user is
    able to action (e.g. merge) over the entire pagure instance.

    ::

        GET /api/0/user/<username>/requests/actionable

    ::

        GET /api/0/user/dudemcpants/requests/actionable

    Parameters
    ^^^^^^^^^^

    +---------------+----------+--------------+----------------------------+
    | Key           | Type     | Optionality  | Description                |
    +===============+==========+==============+============================+
    | ``username``  | string   | Mandatory    | | The username of the user |
    |               |          |              |   whose activity you are   |
    |               |          |              |   interested in.           |
    +---------------+----------+--------------+----------------------------+
    | ``page``      | integer  | Mandatory    | | The page requested.      |
    |               |          |              |   Defaults to 1.           |
    +---------------+----------+--------------+----------------------------+
    | ``status``    | string   | Optional     | | Filter the status of     |
    |               |          |              |   pull requests. Default:  |
    |               |          |              |   ``Open`` (open pull      |
    |               |          |              |   requests), can be        |
    |               |          |              |   ``Closed`` for closed    |
    |               |          |              |   requests, ``Merged``     |
    |               |          |              |   for merged requests, or  |
    |               |          |              |   ``Open`` for open        |
    |               |          |              |   requests.                |
    |               |          |              |   ``All`` returns closed,  |
    |               |          |              |   merged and open requests.|
    +---------------+----------+--------------+----------------------------+

    Sample response
    ^^^^^^^^^^^^^^^

    ::

        {
          "args": {
            "status": "open",
            "username": "ryanlerch",
            "page": 1,
          },
          "requests": [
            {
              "assignee": null,
              "branch": "master",
              "branch_from": "master",
              "closed_at": null,
              "closed_by": null,
              "comments": [],
              "commit_start": "3973fae98fc485783ca14f5c3612d85832185065",
              "commit_stop": "3973fae98fc485783ca14f5c3612d85832185065",
              "date_created": "1510227832",
              "id": 2,
              "initial_comment": null,
              "last_updated": "1510227833",
              "project": {
                  "access_groups": {
                    "admin": [],
                    "commit": [],
                    "ticket": []
                  },
                  "access_users": {
                    "admin": [],
                    "commit": [],
                    "owner": [
                        "ryanlerch"
                    ],
                    "ticket": []
                  },
                  "close_status": [],
                  "custom_keys": [],
                  "date_created": "1510227638",
                  "date_modified": "1510227638",
                  "description": "this is a quick project",
                  "fullname": "aquickproject",
                  "id": 1,
                  "milestones": {},
                  "name": "aquickproject",
                  "namespace": null,
                  "parent": null,
                  "priorities": {},
                  "tags": [],
                  "url_path": "aquickproject",
                  "user": {
                    "fullname": "ryanlerch",
                    "name": "ryanlerch"
                  }
              },
              "remote_git": null,
              "repo_from": {
                  "access_groups": {
                    "admin": [],
                    "commit": [],
                    "ticket": []
                  },
                  "access_users": {
                    "admin": [],
                    "commit": [],
                    "owner": [
                      "dudemcpants"
                    ],
                    "ticket": []
                  },
                  "close_status": [],
                  "custom_keys": [],
                  "date_created": "1510227729",
                  "date_modified": "1510227729",
                  "description": "this is a quick project",
                  "fullname": "forks/dudemcpants/aquickproject",
                  "id": 2,
                  "milestones": {},
                  "name": "aquickproject",
                  "namespace": null,
                  "parent": {
                    "access_groups": {
                      "admin": [],
                      "commit": [],
                      "ticket": []
                    },
                    "access_users": {
                      "admin": [],
                      "commit": [],
                      "owner": [
                        "ryanlerch"
                      ],
                      "ticket": []
                    },
                    "close_status": [],
                    "custom_keys": [],
                    "date_created": "1510227638",
                    "date_modified": "1510227638",
                    "description": "this is a quick project",
                    "fullname": "aquickproject",
                    "id": 1,
                    "milestones": {},
                    "name": "aquickproject",
                    "namespace": null,
                    "parent": null,
                    "priorities": {},
                    "tags": [],
                    "url_path": "aquickproject",
                    "user": {
                      "fullname": "ryanlerch",
                      "name": "ryanlerch"
                    }
                  },
                  "priorities": {},
                  "tags": [],
                  "url_path": "fork/dudemcpants/aquickproject",
                  "user": {
                    "fullname": "Dude McPants",
                    "name": "dudemcpants"
                  }
              },
              "status": "Open",
              "title": "Update README.md",
              "uid": "819e0b1c449e414fa291c914f28d73ec",
              "updated_on": "1510227832",
              "user": {
                "fullname": "Dude McPants",
                "name": "dudemcpants"
              }
            }
          ],
          "total_requests": 1
        }

    """
    status = flask.request.args.get('status', 'open')
    page = flask.request.args.get('page', 1)

    try:
        page = int(page)
        if page <= 0:
            raise ValueError()
    except ValueError:
        raise pagure.exceptions.APIError(
            400, error_code=APIERROR.ENOCODE,
            error='Invalid page requested')

    offset = (page - 1) * 50
    limit = page * 50

    orig_status = status
    if status.lower() == 'all':
        status = None
    else:
        status = status.capitalize()

    pullrequests = pagure.lib.get_pull_request_of_user(
        flask.g.session,
        username=username,
        status=status,
        offset=offset,
        limit=limit,
    )

    pullrequestslist = [
        pr.to_json(public=True, api=True)
        for pr in pullrequests
        if pr.user.username != username
    ]

    return flask.jsonify({
        'total_requests': len(pullrequestslist),
        'requests': pullrequestslist,
        'args': {
            'username': username,
            'status': orig_status,
            'page': page,
        }
    })