~sircmpwn/sr.ht-dev

This thread contains a patchset. You're looking at the original emails, but you may wish to use the patch review UI. Review patch
3 3

[PATCH hub.sr.ht] services.py: rewrite legacy API usage

Details
Message ID
<20240827085445.27138-1-sir@cmpwn.com>
DKIM signature
pass
Download raw message
Patch: +299 -146
This leaves in place some legacy API use regarding the handling of
ticket references in git commits, which is a bit more complex to address
and will be handled in a follow-up patch.
---
 hubsrht/blueprints/mailing_lists.py     |  14 +-
 hubsrht/blueprints/sources.py           |   2 +-
 hubsrht/blueprints/trackers.py          |   6 +-
 hubsrht/services.py                     | 419 ++++++++++++++++--------
 hubsrht/templates/mailing-list-new.html |   2 +-
 hubsrht/templates/tracker-new.html      |   2 +-
 6 files changed, 299 insertions(+), 146 deletions(-)

diff --git a/hubsrht/blueprints/mailing_lists.py b/hubsrht/blueprints/mailing_lists.py
index f6164cb..23c65da 100644
--- a/hubsrht/blueprints/mailing_lists.py
+++ b/hubsrht/blueprints/mailing_lists.py
@@ -93,14 +93,14 @@ Mailing list for end-user discussion and questions related to the
    for list_name in template:
        desc = descs[list_name]
        list_name = list_name.lower() # Per lists.sr.ht naming rules
        try:
            mailing_list = lists.get_list(owner, list_name)
        mailing_list = lists.get_list(owner, list_name)
        if mailing_list is not None:
            in_project = [l.remote_id for l in (MailingList.query
                    .filter(MailingList.project_id == project.id)
                    .filter(MailingList.name == list_name)).all()]
            if in_project:
                continue
        except:
        else:
            valid = Validation({
                "name": list_name,
                "description": desc,
@@ -115,7 +115,7 @@ Mailing list for end-user discussion and questions related to the
        ml.owner_id = project.owner_id
        ml.name = mailing_list["name"]
        ml.description = mailing_list["description"]
        if any(mailing_list["permissions"]["nonsubscriber"]):
        if mailing_list["defaultACL"]["browse"]:
            ml.visibility = Visibility.PUBLIC
        else:
            ml.visibility = Visibility.UNLISTED
@@ -189,7 +189,7 @@ def new_POST(owner, project_name):
    ml.owner_id = project.owner_id
    ml.name = mailing_list["name"]
    ml.description = mailing_list["description"]
    if any(mailing_list["permissions"]["nonsubscriber"]):
    if mailing_list["defaultACL"]["browse"]:
        ml.visibility = Visibility.PUBLIC
    else:
        ml.visibility = Visibility.UNLISTED
@@ -257,7 +257,7 @@ def delete_POST(owner, project_name, list_id):
    if not mailing_list:
        abort(404)

    list_name = mailing_list.name
    list_id = mailing_list.remote_id
    lists.unensure_mailing_list_webhooks(mailing_list)
    db.session.delete(mailing_list)
    db.session.commit()
@@ -265,7 +265,7 @@ def delete_POST(owner, project_name, list_id):
    valid = Validation(request)
    delete_remote = valid.optional("delete-remote") == "on"
    if delete_remote:
        lists.delete_list(owner, list_name)
        lists.delete_list(owner, list_id)

    return redirect(url_for("projects.summary_GET",
        owner=owner.canonical_name, project_name=project.name))
diff --git a/hubsrht/blueprints/sources.py b/hubsrht/blueprints/sources.py
index 1e6d004..8ce5795 100644
--- a/hubsrht/blueprints/sources.py
+++ b/hubsrht/blueprints/sources.py
@@ -275,7 +275,7 @@ def delete_POST(owner, project_name, repo_id):
        if repo.repo_type == RepoType.git:
            git.delete_repo(owner, repo_id)
        elif repo.repo_type == RepoType.hg: 
            hg.delete_repo(owner, repo_name)
            hg.delete_repo(owner, repo_id)
        else:
            assert False

diff --git a/hubsrht/blueprints/trackers.py b/hubsrht/blueprints/trackers.py
index 272a2c3..17a51c4 100644
--- a/hubsrht/blueprints/trackers.py
+++ b/hubsrht/blueprints/trackers.py
@@ -89,7 +89,7 @@ def new_POST(owner, project_name):
    tracker.owner_id = owner.id
    tracker.name = remote_tracker["name"]
    tracker.description = remote_tracker["description"]
    if any(remote_tracker["default_access"]):
    if remote_tracker["defaultACL"]["browse"]:
        tracker.visibility = Visibility.PUBLIC
    else:
        tracker.visibility = Visibility.UNLISTED
@@ -156,14 +156,14 @@ def delete_POST(owner, project_name, tracker_id):
        .filter(Tracker.project_id == project.id)).one_or_none()
    if not tracker:
        abort(404)
    tracker_name = tracker.name
    tracker_name = tracker.remote_id
    db.session.delete(tracker)
    db.session.commit()

    valid = Validation(request)
    delete_remote = valid.optional("delete-remote") == "on"
    if delete_remote:
        todo.delete_tracker(owner, tracker_name)
        todo.delete_tracker(owner, tracker_id)

    return redirect(url_for("projects.summary_GET",
        owner=owner.canonical_name, project_name=project.name))
diff --git a/hubsrht/services.py b/hubsrht/services.py
index 76bf568..11262df 100644
--- a/hubsrht/services.py
+++ b/hubsrht/services.py
@@ -355,14 +355,43 @@ class HgService(SrhtService):
        super().__init__("hg.sr.ht")

    def get_repos(self, user):
        return get_results(f"{_hgsrht}/api/repos", user)
        repos = self.enumerate(user, """
            query GetRepos($cursor: Cursor) {
                me {
                    repositories(cursor: $cursor) {
                        results {
                            id
                            name
                            updated
                            owner {
                                canonicalName
                            }
                        }
                        cursor
                    }
                }
            }
        """, collector=lambda result: result["me"]["repositories"])

        for repo in repos:
            repo["updated"] = gql_time(repo["updated"])

        return repos

    def get_repo(self, user, repo_name):
        r = self.session.get(f"{_hgsrht}/api/repos/{repo_name}",
                headers=encrypt_request_authorization(user))
        if r.status_code != 200:
            raise Exception(r.text)
        return r.json()
        resp = self.exec(user, """
            query GetRepo($repoName: String!) {
                me {
                    repository(name: $repoName) {
                        id
                        name
                        description
                        visibility
                    }
                }
            }
        """, repoName=repo_name)
        return resp["me"]["repository"]

    def get_readme(self, user, repo_name, repo_url):
        # TODO: Cache?
@@ -386,17 +415,35 @@ class HgService(SrhtService):
        description = valid.require("description")
        if not valid.ok:
            return None
        return self.post(user, valid, f"{_hgsrht}/api/repos", {
            "name": name,
            "description": description,
            "visibility": visibility.value,
        })

    def delete_repo(self, user, repo_name):
        r = self.session.delete(f"{_hgsrht}/api/repos/{repo_name}",
                headers=encrypt_request_authorization(user))
        if r.status_code != 204 and r.status_code != 404:
            raise Exception(r.text)
        resp = self.exec(user, """
        mutation CreateRepo(
                $name: String!,
                $description: String!,
                $visibility: Visibility!) {
            createRepository(name: $name,
                    description: $description,
                    visibility: $visibility) {
                id, name, description, visibility
            }
        }
        """,
        name=name,
        description=description,
        visibility=visibility.value,
        valid=valid)

        if not valid.ok:
            return None

        return resp["createRepository"]

    def delete_repo(self, user, repo_id):
        self.exec(user, """
        mutation DeleteRepo($repo_id: Int!) {
            deleteRepository(id: $repo_id) { id }
        }
        """, repo_id=repo_id)

    def ensure_user_webhooks(self, user):
        config = {
@@ -417,14 +464,119 @@ class ListService(SrhtService):
        super().__init__("lists.sr.ht")

    def get_lists(self, user):
        return get_results(f"{_listsrht}/api/lists", user)
        lists = self.enumerate(user, """
        query GetLists($cursor: Cursor) {
            me {
                lists(cursor: $cursor) {
                    results {
                        id
                        name
                        description
                        updated
                        owner {
                            canonicalName
                        }
                    }
                    cursor
                }
            }
        }
        """,
        collector=lambda result: result["me"]["lists"])

        for ml in lists:
            ml["updated"] = gql_time(ml["updated"])

        return lists

    def get_list(self, user, list_name):
        r = self.session.get(f"{_listsrht}/api/lists/{list_name}",
                headers=encrypt_request_authorization(user))
        if r.status_code != 200:
            raise Exception(r.json())
        return r.json()
        resp = self.exec(user, """
        query GetList($listName: String!) {
            me {
                list(name: $listName) {
                    id
                    name
                    description
                    visibility
                    defaultACL {
                        browse
                    }
                }
            }
        }
        """, listName=list_name)
        return resp["me"]["list"]

    def create_list(self, user, valid):
        name = valid.require("name")
        description = valid.optional("description")
        if not valid.ok:
            return None

        resp = self.exec(user, """
        mutation CreateList(
                $name: String!,
                $description: String!) {
            createMailingList(name: $name,
                    description: $description,
                    visibility: PUBLIC) {
                id
                name
                description
                visibility
                defaultACL {
                    browse
                }
            }
        }
        """,
        name=name,
        description=description,
        valid=valid)

        if not valid.ok:
            return None

        return resp["createMailingList"]

    def delete_list(self, user, list_id):
        resp = self.exec(user, """
        mutation DeleteList($list_id: Int!) {
            deleteMailingList(id: $list_id) { id }
        }
        """, list_id=list_id)

    def patchset_create_tool(self, user, patchset_id, icon, details):
        resp = self.exec(user, """
        mutation CreateTool(
                $patchset_id: Int!,
                $details: String!,
                $icon: ToolIcon!) {
            createTool(patchsetID: $patchsetID, details: $details, icon: $icon) {
                id
            }
        }
        """,
        patchsetID=patchset_id,
        icon=icon,
        details=details)
        return r["createTool"]["id"]

    def patchset_update_tool(self, user, tool_id, icon, details):
        resp = self.exec(user, """
        mutation UpdateTool(
                $toolID: Int!,
                $details: String!,
                $icon: ToolIcon!) {
            updateTool(id: $toolID, details: $details, icon: $icon) {
                id
            }
        }
        """,
        toolID=tool_id,
        icon=icon,
        details=details)
        return resp["updateTool"]["id"]

    def ensure_mailing_list_webhooks(self, mailing_list):
        config = {
@@ -444,92 +596,136 @@ class ListService(SrhtService):
        except:
            pass # nbd, upstream was presumably deleted

    def create_list(self, user, valid):
class TodoService(SrhtService):
    def __init__(self):
        super().__init__("todo.sr.ht")

    def get_trackers(self, user):
        trackers = self.enumerate(user, """
        query GetTrackers($cursor: Cursor) {
            me {
                trackers(cursor: $cursor) {
                    results {
                        id
                        name
                        description
                        updated
                        owner {
                            canonicalName
                        }
                    }
                    cursor
                }
            }
        }
        """, collector=lambda result: result["me"]["trackers"])

        for tr in trackers:
            tr["updated"] = gql_time(tr["updated"])

        return trackers

    def get_tracker(self, user, tracker_name):
        resp = self.exec(user, """
        query GetTracker($trackerName: String!) {
            me {
                tracker(name: $trackerName) {
                    id
                    name
                    description
                    visibility
                    defaultACL {
                        browse
                    }
                }
            }
        }
        """, trackerName=tracker_name)
        return resp["me"]["tracker"]

    def create_tracker(self, user, valid, visibility):
        name = valid.require("name")
        description = valid.optional("description")
        if not valid.ok:
            return None
        return self.post(user, valid, f"{_listsrht}/api/lists", {
            "name": name,
            "description": description,
        })

    def delete_list(self, user, list_name):
        r = self.session.delete(f"{_listsrht}/api/lists/{list_name}",
                headers=encrypt_request_authorization(user))
        if r.status_code != 204 and r.status_code != 404:
            raise Exception(r.text)

    def patchset_create_tool(self, user, patchset_id, icon, details):
        query = """
        mutation CreateTool($id: Int!, $details: String!, $icon: ToolIcon!) {
            createTool(patchsetID: $id, details: $details, icon: $icon) {
        resp = self.exec(user, """
        mutation CreateTracker(
                $name: String!,
                $description: String!,
                $visibility: Visibility!) {
            createTracker(name: $name,
                    description: $description,
                    visibility: $visibility) {
                id
                name
                description
                visibility
                defaultACL {
                    browse
                }
            }
        }
        """
        r = self.post(user, None, f"{_listsrht_api}/query", {
            "query": query,
            "variables": {
                "id": patchset_id,
                "icon": icon,
                "details": details,
            },
        })
        if not r["data"] or not r["data"]["createTool"]:
        """,
        name=name,
        description=description,
        visibility=visibility.value,
        valid=valid)

        if not valid.ok:
            return None
        return r["data"]["createTool"]["id"]

    def patchset_update_tool(self, user, tool_id, icon, details):
        return resp["createTracker"]

    def delete_tracker(self, user, tracker_id):
        self.exec(user, """
        mutation DeleteTracker($trackerID: Int!) {
            deleteTracker(id: $trackerID) { id }
        }
        """, trackerID=tracker_id)

    def get_ticket_comments(self, user, owner, tracker, ticket):
        query = """
        mutation UpdateTool($id: Int!, $details: String!, $icon: ToolIcon!) {
            updateTool(id: $id, details: $details, icon: $icon) {
                id
        query TicketComments($username: String!, $tracker: String!, $ticket: Int!) {
          user(username: $username) {
            tracker(name: $tracker) {
              ticket(id: $ticket) {
                events {
                  results {
                    changes {
                      ... on Comment {
                        text
                      }
                    }
                  }
                }
              }
            }
          }
        }
        """
        r = self.post(user, None, f"{_listsrht_api}/query", {
        r = self.post(user, None, f"{_todosrht_api}/query", {
            "query": query,
            "variables": {
                "id": tool_id,
                "icon": icon,
                "details": details,
            },
        })
        if not r["data"] or not r["data"]["updateTool"]:
            return None
        return r["data"]["updateTool"]["id"]

class TodoService(SrhtService):
    def __init__(self):
        super().__init__("todo.sr.ht")

    def get_trackers(self, user):
        return get_results(f"{_todosrht}/api/trackers", user)

    def get_tracker(self, user, tracker_name):
        r = self.session.get(f"{_todosrht}/api/trackers/{tracker_name}",
                headers=encrypt_request_authorization(user))
        if r.status_code != 200:
            raise Exception(r.json())
        return r.json()

    def create_tracker(self, user, valid, visibility):
        name = valid.require("name")
        description = valid.optional("description")
        if not valid.ok:
            return None
        return self.post(user, valid, f"{_todosrht}/api/trackers", {
            "name": name,
            "description": description,
            "visibility": visibility.value.upper(),
                "username": owner[1:],
                "tracker": tracker,
                "ticket": ticket,
            }
        })
        comments = []
        for e in r["data"]["user"]["tracker"]["ticket"]["events"]["results"]:
            for c in e["changes"]:
                if "text" in c:
                    comments.append(c["text"])
        return comments

    def delete_tracker(self, user, tracker_name):
        r = self.session.delete(f"{_todosrht}/api/trackers/{tracker_name}",
                headers=encrypt_request_authorization(user))
        if r.status_code != 204 and r.status_code != 404:
            raise Exception(r.text)
    def update_ticket(self, user, owner, tracker, ticket, comment, resolution=None):
        url = f"{_todosrht}/api/user/{owner}/trackers/{tracker}/tickets/{ticket}"
        payload = {"comment": comment}
        if resolution is not None:
            payload["resolution"] = resolution
            payload["status"] = "resolved"
        self.put(user, None, url, payload)

    def ensure_user_webhooks(self, user):
        config = {
@@ -583,49 +779,6 @@ class TodoService(SrhtService):
        except:
            pass # nbd, upstream was presumably deleted

    def get_ticket_comments(self, user, owner, tracker, ticket):
        query = """
        query TicketComments($username: String!, $tracker: String!, $ticket: Int!) {
          user(username: $username) {
            tracker(name: $tracker) {
              ticket(id: $ticket) {
                events {
                  results {
                    changes {
                      ... on Comment {
                        text
                      }
                    }
                  }
                }
              }
            }
          }
        }
        """
        r = self.post(user, None, f"{_todosrht_api}/query", {
            "query": query,
            "variables": {
                "username": owner[1:],
                "tracker": tracker,
                "ticket": ticket,
            }
        })
        comments = []
        for e in r["data"]["user"]["tracker"]["ticket"]["events"]["results"]:
            for c in e["changes"]:
                if "text" in c:
                    comments.append(c["text"])
        return comments

    def update_ticket(self, user, owner, tracker, ticket, comment, resolution=None):
        url = f"{_todosrht}/api/user/{owner}/trackers/{tracker}/tickets/{ticket}"
        payload = {"comment": comment}
        if resolution is not None:
            payload["resolution"] = resolution
            payload["status"] = "resolved"
        self.put(user, None, url, payload)

class BuildService(SrhtService):
    def __init__(self):
        super().__init__("builds.sr.ht")
diff --git a/hubsrht/templates/mailing-list-new.html b/hubsrht/templates/mailing-list-new.html
index 08452ab..aacb994 100644
--- a/hubsrht/templates/mailing-list-new.html
+++ b/hubsrht/templates/mailing-list-new.html
@@ -142,7 +142,7 @@
          {% endif %}
          <a
            href="{{get_origin("lists.sr.ht",
              external=True)}}/{{ list["owner"]["canonical_name"] }}/{{list["name"]}}"
              external=True)}}/{{ list["owner"]["canonicalName"] }}/{{list["name"]}}"
            target="_blank"
            rel="noopener"
          >{{ list["name"] }}</a>
diff --git a/hubsrht/templates/tracker-new.html b/hubsrht/templates/tracker-new.html
index 61e2397..4045749 100644
--- a/hubsrht/templates/tracker-new.html
+++ b/hubsrht/templates/tracker-new.html
@@ -78,7 +78,7 @@
          {% endif %}
          <a
            href="{{get_origin("todo.sr.ht",
              external=True)}}/{{ tracker["owner"]["canonical_name"] }}/{{tracker["name"]}}"
              external=True)}}/{{ tracker["owner"]["canonicalName"] }}/{{tracker["name"]}}"
            target="_blank"
            rel="noopener"
          >{{ tracker["name"] }}</a>
-- 
2.46.0

[hub.sr.ht/patches/alpine.yml] build failed

builds.sr.ht <builds@sr.ht>
Details
Message ID
<D3QKH2HLOU9H.1OQPHOD297E3A@fra01>
In-Reply-To
<20240827085445.27138-1-sir@cmpwn.com> (view parent)
DKIM signature
missing
Download raw message
hub.sr.ht/patches/alpine.yml: FAILED in 51s

[services.py: rewrite legacy API usage][0] from [Drew DeVault][1]

[0]: https://lists.sr.ht/~sircmpwn/sr.ht-dev/patches/54734
[1]: sir@cmpwn.com

✗ #1312749 FAILED hub.sr.ht/patches/alpine.yml https://builds.sr.ht/~sircmpwn/job/1312749
Details
Message ID
<28352f21-7a0d-42d3-9fdb-41b0a95f65f3@bitfehler.net>
In-Reply-To
<20240827085445.27138-1-sir@cmpwn.com> (view parent)
DKIM signature
pass
Download raw message
LGTM. Re the commit message, I'll just point out that it also leaves in 
place using the legacy API for lists.ensure_webhooks, but I suppose that 
cannot be changed because this is about legacy webhooks?

Either way, ship it.

On 8/27/24 10:54 AM, Drew DeVault wrote:
> This leaves in place some legacy API use regarding the handling of
> ticket references in git commits, which is a bit more complex to address
> and will be handled in a follow-up patch.
> ---
>   hubsrht/blueprints/mailing_lists.py     |  14 +-
>   hubsrht/blueprints/sources.py           |   2 +-
>   hubsrht/blueprints/trackers.py          |   6 +-
>   hubsrht/services.py                     | 419 ++++++++++++++++--------
>   hubsrht/templates/mailing-list-new.html |   2 +-
>   hubsrht/templates/tracker-new.html      |   2 +-
>   6 files changed, 299 insertions(+), 146 deletions(-)
> 
> diff --git a/hubsrht/blueprints/mailing_lists.py b/hubsrht/blueprints/mailing_lists.py
> index f6164cb..23c65da 100644
> --- a/hubsrht/blueprints/mailing_lists.py
> +++ b/hubsrht/blueprints/mailing_lists.py
> @@ -93,14 +93,14 @@ Mailing list for end-user discussion and questions related to the
>       for list_name in template:
>           desc = descs[list_name]
>           list_name = list_name.lower() # Per lists.sr.ht naming rules
> -        try:
> -            mailing_list = lists.get_list(owner, list_name)
> +        mailing_list = lists.get_list(owner, list_name)
> +        if mailing_list is not None:
>               in_project = [l.remote_id for l in (MailingList.query
>                       .filter(MailingList.project_id == project.id)
>                       .filter(MailingList.name == list_name)).all()]
>               if in_project:
>                   continue
> -        except:
> +        else:
>               valid = Validation({
>                   "name": list_name,
>                   "description": desc,
> @@ -115,7 +115,7 @@ Mailing list for end-user discussion and questions related to the
>           ml.owner_id = project.owner_id
>           ml.name = mailing_list["name"]
>           ml.description = mailing_list["description"]
> -        if any(mailing_list["permissions"]["nonsubscriber"]):
> +        if mailing_list["defaultACL"]["browse"]:
>               ml.visibility = Visibility.PUBLIC
>           else:
>               ml.visibility = Visibility.UNLISTED
> @@ -189,7 +189,7 @@ def new_POST(owner, project_name):
>       ml.owner_id = project.owner_id
>       ml.name = mailing_list["name"]
>       ml.description = mailing_list["description"]
> -    if any(mailing_list["permissions"]["nonsubscriber"]):
> +    if mailing_list["defaultACL"]["browse"]:
>           ml.visibility = Visibility.PUBLIC
>       else:
>           ml.visibility = Visibility.UNLISTED
> @@ -257,7 +257,7 @@ def delete_POST(owner, project_name, list_id):
>       if not mailing_list:
>           abort(404)
>   
> -    list_name = mailing_list.name
> +    list_id = mailing_list.remote_id
>       lists.unensure_mailing_list_webhooks(mailing_list)
>       db.session.delete(mailing_list)
>       db.session.commit()
> @@ -265,7 +265,7 @@ def delete_POST(owner, project_name, list_id):
>       valid = Validation(request)
>       delete_remote = valid.optional("delete-remote") == "on"
>       if delete_remote:
> -        lists.delete_list(owner, list_name)
> +        lists.delete_list(owner, list_id)
>   
>       return redirect(url_for("projects.summary_GET",
>           owner=owner.canonical_name, project_name=project.name))
> diff --git a/hubsrht/blueprints/sources.py b/hubsrht/blueprints/sources.py
> index 1e6d004..8ce5795 100644
> --- a/hubsrht/blueprints/sources.py
> +++ b/hubsrht/blueprints/sources.py
> @@ -275,7 +275,7 @@ def delete_POST(owner, project_name, repo_id):
>           if repo.repo_type == RepoType.git:
>               git.delete_repo(owner, repo_id)
>           elif repo.repo_type == RepoType.hg:
> -            hg.delete_repo(owner, repo_name)
> +            hg.delete_repo(owner, repo_id)
>           else:
>               assert False
>   
> diff --git a/hubsrht/blueprints/trackers.py b/hubsrht/blueprints/trackers.py
> index 272a2c3..17a51c4 100644
> --- a/hubsrht/blueprints/trackers.py
> +++ b/hubsrht/blueprints/trackers.py
> @@ -89,7 +89,7 @@ def new_POST(owner, project_name):
>       tracker.owner_id = owner.id
>       tracker.name = remote_tracker["name"]
>       tracker.description = remote_tracker["description"]
> -    if any(remote_tracker["default_access"]):
> +    if remote_tracker["defaultACL"]["browse"]:
>           tracker.visibility = Visibility.PUBLIC
>       else:
>           tracker.visibility = Visibility.UNLISTED
> @@ -156,14 +156,14 @@ def delete_POST(owner, project_name, tracker_id):
>           .filter(Tracker.project_id == project.id)).one_or_none()
>       if not tracker:
>           abort(404)
> -    tracker_name = tracker.name
> +    tracker_name = tracker.remote_id
>       db.session.delete(tracker)
>       db.session.commit()
>   
>       valid = Validation(request)
>       delete_remote = valid.optional("delete-remote") == "on"
>       if delete_remote:
> -        todo.delete_tracker(owner, tracker_name)
> +        todo.delete_tracker(owner, tracker_id)
>   
>       return redirect(url_for("projects.summary_GET",
>           owner=owner.canonical_name, project_name=project.name))
> diff --git a/hubsrht/services.py b/hubsrht/services.py
> index 76bf568..11262df 100644
> --- a/hubsrht/services.py
> +++ b/hubsrht/services.py
> @@ -355,14 +355,43 @@ class HgService(SrhtService):
>           super().__init__("hg.sr.ht")
>   
>       def get_repos(self, user):
> -        return get_results(f"{_hgsrht}/api/repos", user)
> +        repos = self.enumerate(user, """
> +            query GetRepos($cursor: Cursor) {
> +                me {
> +                    repositories(cursor: $cursor) {
> +                        results {
> +                            id
> +                            name
> +                            updated
> +                            owner {
> +                                canonicalName
> +                            }
> +                        }
> +                        cursor
> +                    }
> +                }
> +            }
> +        """, collector=lambda result: result["me"]["repositories"])
> +
> +        for repo in repos:
> +            repo["updated"] = gql_time(repo["updated"])
> +
> +        return repos
>   
>       def get_repo(self, user, repo_name):
> -        r = self.session.get(f"{_hgsrht}/api/repos/{repo_name}",
> -                headers=encrypt_request_authorization(user))
> -        if r.status_code != 200:
> -            raise Exception(r.text)
> -        return r.json()
> +        resp = self.exec(user, """
> +            query GetRepo($repoName: String!) {
> +                me {
> +                    repository(name: $repoName) {
> +                        id
> +                        name
> +                        description
> +                        visibility
> +                    }
> +                }
> +            }
> +        """, repoName=repo_name)
> +        return resp["me"]["repository"]
>   
>       def get_readme(self, user, repo_name, repo_url):
>           # TODO: Cache?
> @@ -386,17 +415,35 @@ class HgService(SrhtService):
>           description = valid.require("description")
>           if not valid.ok:
>               return None
> -        return self.post(user, valid, f"{_hgsrht}/api/repos", {
> -            "name": name,
> -            "description": description,
> -            "visibility": visibility.value,
> -        })
>   
> -    def delete_repo(self, user, repo_name):
> -        r = self.session.delete(f"{_hgsrht}/api/repos/{repo_name}",
> -                headers=encrypt_request_authorization(user))
> -        if r.status_code != 204 and r.status_code != 404:
> -            raise Exception(r.text)
> +        resp = self.exec(user, """
> +        mutation CreateRepo(
> +                $name: String!,
> +                $description: String!,
> +                $visibility: Visibility!) {
> +            createRepository(name: $name,
> +                    description: $description,
> +                    visibility: $visibility) {
> +                id, name, description, visibility
> +            }
> +        }
> +        """,
> +        name=name,
> +        description=description,
> +        visibility=visibility.value,
> +        valid=valid)
> +
> +        if not valid.ok:
> +            return None
> +
> +        return resp["createRepository"]
> +
> +    def delete_repo(self, user, repo_id):
> +        self.exec(user, """
> +        mutation DeleteRepo($repo_id: Int!) {
> +            deleteRepository(id: $repo_id) { id }
> +        }
> +        """, repo_id=repo_id)
>   
>       def ensure_user_webhooks(self, user):
>           config = {
> @@ -417,14 +464,119 @@ class ListService(SrhtService):
>           super().__init__("lists.sr.ht")
>   
>       def get_lists(self, user):
> -        return get_results(f"{_listsrht}/api/lists", user)
> +        lists = self.enumerate(user, """
> +        query GetLists($cursor: Cursor) {
> +            me {
> +                lists(cursor: $cursor) {
> +                    results {
> +                        id
> +                        name
> +                        description
> +                        updated
> +                        owner {
> +                            canonicalName
> +                        }
> +                    }
> +                    cursor
> +                }
> +            }
> +        }
> +        """,
> +        collector=lambda result: result["me"]["lists"])
> +
> +        for ml in lists:
> +            ml["updated"] = gql_time(ml["updated"])
> +
> +        return lists
>   
>       def get_list(self, user, list_name):
> -        r = self.session.get(f"{_listsrht}/api/lists/{list_name}",
> -                headers=encrypt_request_authorization(user))
> -        if r.status_code != 200:
> -            raise Exception(r.json())
> -        return r.json()
> +        resp = self.exec(user, """
> +        query GetList($listName: String!) {
> +            me {
> +                list(name: $listName) {
> +                    id
> +                    name
> +                    description
> +                    visibility
> +                    defaultACL {
> +                        browse
> +                    }
> +                }
> +            }
> +        }
> +        """, listName=list_name)
> +        return resp["me"]["list"]
> +
> +    def create_list(self, user, valid):
> +        name = valid.require("name")
> +        description = valid.optional("description")
> +        if not valid.ok:
> +            return None
> +
> +        resp = self.exec(user, """
> +        mutation CreateList(
> +                $name: String!,
> +                $description: String!) {
> +            createMailingList(name: $name,
> +                    description: $description,
> +                    visibility: PUBLIC) {
> +                id
> +                name
> +                description
> +                visibility
> +                defaultACL {
> +                    browse
> +                }
> +            }
> +        }
> +        """,
> +        name=name,
> +        description=description,
> +        valid=valid)
> +
> +        if not valid.ok:
> +            return None
> +
> +        return resp["createMailingList"]
> +
> +    def delete_list(self, user, list_id):
> +        resp = self.exec(user, """
> +        mutation DeleteList($list_id: Int!) {
> +            deleteMailingList(id: $list_id) { id }
> +        }
> +        """, list_id=list_id)
> +
> +    def patchset_create_tool(self, user, patchset_id, icon, details):
> +        resp = self.exec(user, """
> +        mutation CreateTool(
> +                $patchset_id: Int!,
> +                $details: String!,
> +                $icon: ToolIcon!) {
> +            createTool(patchsetID: $patchsetID, details: $details, icon: $icon) {
> +                id
> +            }
> +        }
> +        """,
> +        patchsetID=patchset_id,
> +        icon=icon,
> +        details=details)
> +        return r["createTool"]["id"]
> +
> +    def patchset_update_tool(self, user, tool_id, icon, details):
> +        resp = self.exec(user, """
> +        mutation UpdateTool(
> +                $toolID: Int!,
> +                $details: String!,
> +                $icon: ToolIcon!) {
> +            updateTool(id: $toolID, details: $details, icon: $icon) {
> +                id
> +            }
> +        }
> +        """,
> +        toolID=tool_id,
> +        icon=icon,
> +        details=details)
> +        return resp["updateTool"]["id"]
>   
>       def ensure_mailing_list_webhooks(self, mailing_list):
>           config = {
> @@ -444,92 +596,136 @@ class ListService(SrhtService):
>           except:
>               pass # nbd, upstream was presumably deleted
>   
> -    def create_list(self, user, valid):
> +class TodoService(SrhtService):
> +    def __init__(self):
> +        super().__init__("todo.sr.ht")
> +
> +    def get_trackers(self, user):
> +        trackers = self.enumerate(user, """
> +        query GetTrackers($cursor: Cursor) {
> +            me {
> +                trackers(cursor: $cursor) {
> +                    results {
> +                        id
> +                        name
> +                        description
> +                        updated
> +                        owner {
> +                            canonicalName
> +                        }
> +                    }
> +                    cursor
> +                }
> +            }
> +        }
> +        """, collector=lambda result: result["me"]["trackers"])
> +
> +        for tr in trackers:
> +            tr["updated"] = gql_time(tr["updated"])
> +
> +        return trackers
> +
> +    def get_tracker(self, user, tracker_name):
> +        resp = self.exec(user, """
> +        query GetTracker($trackerName: String!) {
> +            me {
> +                tracker(name: $trackerName) {
> +                    id
> +                    name
> +                    description
> +                    visibility
> +                    defaultACL {
> +                        browse
> +                    }
> +                }
> +            }
> +        }
> +        """, trackerName=tracker_name)
> +        return resp["me"]["tracker"]
> +
> +    def create_tracker(self, user, valid, visibility):
>           name = valid.require("name")
>           description = valid.optional("description")
>           if not valid.ok:
>               return None
> -        return self.post(user, valid, f"{_listsrht}/api/lists", {
> -            "name": name,
> -            "description": description,
> -        })
> -
> -    def delete_list(self, user, list_name):
> -        r = self.session.delete(f"{_listsrht}/api/lists/{list_name}",
> -                headers=encrypt_request_authorization(user))
> -        if r.status_code != 204 and r.status_code != 404:
> -            raise Exception(r.text)
>   
> -    def patchset_create_tool(self, user, patchset_id, icon, details):
> -        query = """
> -        mutation CreateTool($id: Int!, $details: String!, $icon: ToolIcon!) {
> -            createTool(patchsetID: $id, details: $details, icon: $icon) {
> +        resp = self.exec(user, """
> +        mutation CreateTracker(
> +                $name: String!,
> +                $description: String!,
> +                $visibility: Visibility!) {
> +            createTracker(name: $name,
> +                    description: $description,
> +                    visibility: $visibility) {
>                   id
> +                name
> +                description
> +                visibility
> +                defaultACL {
> +                    browse
> +                }
>               }
>           }
> -        """
> -        r = self.post(user, None, f"{_listsrht_api}/query", {
> -            "query": query,
> -            "variables": {
> -                "id": patchset_id,
> -                "icon": icon,
> -                "details": details,
> -            },
> -        })
> -        if not r["data"] or not r["data"]["createTool"]:
> +        """,
> +        name=name,
> +        description=description,
> +        visibility=visibility.value,
> +        valid=valid)
> +
> +        if not valid.ok:
>               return None
> -        return r["data"]["createTool"]["id"]
>   
> -    def patchset_update_tool(self, user, tool_id, icon, details):
> +        return resp["createTracker"]
> +
> +    def delete_tracker(self, user, tracker_id):
> +        self.exec(user, """
> +        mutation DeleteTracker($trackerID: Int!) {
> +            deleteTracker(id: $trackerID) { id }
> +        }
> +        """, trackerID=tracker_id)
> +
> +    def get_ticket_comments(self, user, owner, tracker, ticket):
>           query = """
> -        mutation UpdateTool($id: Int!, $details: String!, $icon: ToolIcon!) {
> -            updateTool(id: $id, details: $details, icon: $icon) {
> -                id
> +        query TicketComments($username: String!, $tracker: String!, $ticket: Int!) {
> +          user(username: $username) {
> +            tracker(name: $tracker) {
> +              ticket(id: $ticket) {
> +                events {
> +                  results {
> +                    changes {
> +                      ... on Comment {
> +                        text
> +                      }
> +                    }
> +                  }
> +                }
> +              }
>               }
> +          }
>           }
>           """
> -        r = self.post(user, None, f"{_listsrht_api}/query", {
> +        r = self.post(user, None, f"{_todosrht_api}/query", {
>               "query": query,
>               "variables": {
> -                "id": tool_id,
> -                "icon": icon,
> -                "details": details,
> -            },
> -        })
> -        if not r["data"] or not r["data"]["updateTool"]:
> -            return None
> -        return r["data"]["updateTool"]["id"]
> -
> -class TodoService(SrhtService):
> -    def __init__(self):
> -        super().__init__("todo.sr.ht")
> -
> -    def get_trackers(self, user):
> -        return get_results(f"{_todosrht}/api/trackers", user)
> -
> -    def get_tracker(self, user, tracker_name):
> -        r = self.session.get(f"{_todosrht}/api/trackers/{tracker_name}",
> -                headers=encrypt_request_authorization(user))
> -        if r.status_code != 200:
> -            raise Exception(r.json())
> -        return r.json()
> -
> -    def create_tracker(self, user, valid, visibility):
> -        name = valid.require("name")
> -        description = valid.optional("description")
> -        if not valid.ok:
> -            return None
> -        return self.post(user, valid, f"{_todosrht}/api/trackers", {
> -            "name": name,
> -            "description": description,
> -            "visibility": visibility.value.upper(),
> +                "username": owner[1:],
> +                "tracker": tracker,
> +                "ticket": ticket,
> +            }
>           })
> +        comments = []
> +        for e in r["data"]["user"]["tracker"]["ticket"]["events"]["results"]:
> +            for c in e["changes"]:
> +                if "text" in c:
> +                    comments.append(c["text"])
> +        return comments
>   
> -    def delete_tracker(self, user, tracker_name):
> -        r = self.session.delete(f"{_todosrht}/api/trackers/{tracker_name}",
> -                headers=encrypt_request_authorization(user))
> -        if r.status_code != 204 and r.status_code != 404:
> -            raise Exception(r.text)
> +    def update_ticket(self, user, owner, tracker, ticket, comment, resolution=None):
> +        url = f"{_todosrht}/api/user/{owner}/trackers/{tracker}/tickets/{ticket}"
> +        payload = {"comment": comment}
> +        if resolution is not None:
> +            payload["resolution"] = resolution
> +            payload["status"] = "resolved"
> +        self.put(user, None, url, payload)
>   
>       def ensure_user_webhooks(self, user):
>           config = {
> @@ -583,49 +779,6 @@ class TodoService(SrhtService):
>           except:
>               pass # nbd, upstream was presumably deleted
>   
> -    def get_ticket_comments(self, user, owner, tracker, ticket):
> -        query = """
> -        query TicketComments($username: String!, $tracker: String!, $ticket: Int!) {
> -          user(username: $username) {
> -            tracker(name: $tracker) {
> -              ticket(id: $ticket) {
> -                events {
> -                  results {
> -                    changes {
> -                      ... on Comment {
> -                        text
> -                      }
> -                    }
> -                  }
> -                }
> -              }
> -            }
> -          }
> -        }
> -        """
> -        r = self.post(user, None, f"{_todosrht_api}/query", {
> -            "query": query,
> -            "variables": {
> -                "username": owner[1:],
> -                "tracker": tracker,
> -                "ticket": ticket,
> -            }
> -        })
> -        comments = []
> -        for e in r["data"]["user"]["tracker"]["ticket"]["events"]["results"]:
> -            for c in e["changes"]:
> -                if "text" in c:
> -                    comments.append(c["text"])
> -        return comments
> -
> -    def update_ticket(self, user, owner, tracker, ticket, comment, resolution=None):
> -        url = f"{_todosrht}/api/user/{owner}/trackers/{tracker}/tickets/{ticket}"
> -        payload = {"comment": comment}
> -        if resolution is not None:
> -            payload["resolution"] = resolution
> -            payload["status"] = "resolved"
> -        self.put(user, None, url, payload)
> -
>   class BuildService(SrhtService):
>       def __init__(self):
>           super().__init__("builds.sr.ht")
> diff --git a/hubsrht/templates/mailing-list-new.html b/hubsrht/templates/mailing-list-new.html
> index 08452ab..aacb994 100644
> --- a/hubsrht/templates/mailing-list-new.html
> +++ b/hubsrht/templates/mailing-list-new.html
> @@ -142,7 +142,7 @@
>             {% endif %}
>             <a
>               href="{{get_origin("lists.sr.ht",
> -              external=True)}}/{{ list["owner"]["canonical_name"] }}/{{list["name"]}}"
> +              external=True)}}/{{ list["owner"]["canonicalName"] }}/{{list["name"]}}"
>               target="_blank"
>               rel="noopener"
>             >{{ list["name"] }}</a>
> diff --git a/hubsrht/templates/tracker-new.html b/hubsrht/templates/tracker-new.html
> index 61e2397..4045749 100644
> --- a/hubsrht/templates/tracker-new.html
> +++ b/hubsrht/templates/tracker-new.html
> @@ -78,7 +78,7 @@
>             {% endif %}
>             <a
>               href="{{get_origin("todo.sr.ht",
> -              external=True)}}/{{ tracker["owner"]["canonical_name"] }}/{{tracker["name"]}}"
> +              external=True)}}/{{ tracker["owner"]["canonicalName"] }}/{{tracker["name"]}}"
>               target="_blank"
>               rel="noopener"
>             >{{ tracker["name"] }}</a>
Details
Message ID
<D3Y70LIGOA9P.1WMB3R1OU6TX6@cmpwn.com>
In-Reply-To
<28352f21-7a0d-42d3-9fdb-41b0a95f65f3@bitfehler.net> (view parent)
DKIM signature
pass
Download raw message
Got it.

To git@git.sr.ht:~sircmpwn/todo.sr.ht
   dc8988e..47ea6d9  master -> master
Reply to thread Export thread (mbox)