Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
193 changes: 193 additions & 0 deletions ogr/services/forgejo/comments.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
# Copyright Contributors to the Packit project.
# SPDX-License-Identifier: MIT

import datetime
from typing import Any

from ogr.abstract import Comment, CommitComment, IssueComment, PRComment, Reaction


class ForgejoReaction(Reaction):
_raw_reaction: dict

def __init__(
self,
raw_reaction: dict,
forgejo_client: Any,
repository: str,
issue_number: int,
) -> None:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where does this __init__ come from. I don't see a reference to forgejo_client in the current codebase

"""
Initializes a ForgejoReaction.

Args:
raw_reaction (dict): The raw reaction data from the API.
forgejo_client (Any): The Forgejo client instance.
repository (str): Repository identifier (e.g., "owner/repo").
issue_number (int): The issue number associated with the comment.
"""
self._raw_reaction = raw_reaction
self._forgejo_client = forgejo_client
self._repository = repository
self._issue_number = issue_number
self._id = raw_reaction.get("id")
self._content = raw_reaction.get("content")
self._user_login = raw_reaction.get("user", {}).get("login")

def __str__(self) -> str:
return "Forgejo" + super().__str__()

def delete(self) -> None:
"""
Deletes the reaction from Forgejo using the stored client context.
"""
url = (
f"{self._forgejo_client.forgejo_url}/api/v1/repos/"
f"{self._repository}/issues/{self._issue_number}/reactions/{self._id}"
)
self._forgejo_client._make_request("DELETE", url)
Comment on lines +44 to +48
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we are using the client library methods, see examples here, that file can be an inspiration on how to implement the methods. So for this particular case, you would use this method. And similarly, for other methods where you currently construct the URL manually.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you so much for this clarity

Copy link
Contributor

@mynk8 mynk8 Apr 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am working on implementing ForgejoIssue (#896) and it also depends on ForgejoIssueComments, I can collaborate to complete this PR.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you @mynk8 will you have time today?

Copy link
Contributor

@mynk8 mynk8 Apr 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes 👍

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't the dependency be the other way around? Have this PR depend on #896? One simple approach is to rebase this on top of #896 and update it as the PR evolves. This is also a good example why you should have a concise git history

Copy link
Contributor

@mynk8 mynk8 Apr 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's kind of true, I need ForgejoIssueComments for get_comments and _get_all_comments and this is where I think I can help with here.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yeah, I missed that this PR is now implementing comments in general. Up to you how it is easier to coordinate. You could also make it a NotImplementedError and follow it up in a subsequent PR. No need to implement everything in a single PR, as long as the minimal functionality can be tested


@property
def content(self) -> str:
return self._content

@property
def user_login(self) -> str:
return self._user_login

@property
def id(self) -> int:
return self._id


class ForgejoComment(Comment):
def __init__(
self,
raw_comment: dict,
forgejo_client: Any,
repository: str,
issue_number: int,
) -> None:
"""
Initializes a ForgejoComment.

Args:
raw_comment (dict): The raw comment data.
forgejo_client (Any): The Forgejo client instance.
repository (str): Repository identifier (e.g., "owner/repo").
issue_number (int): The issue number associated with the comment.
"""
self._forgejo_client = forgejo_client
self._repository = repository
self._issue_number = issue_number
self._from_raw_comment(raw_comment)

def _from_raw_comment(self, raw_comment: dict) -> None:
"""
Initializes comment attributes from the raw API data.
"""
self._raw_comment = raw_comment
self._id = raw_comment.get("id")
self._author = raw_comment.get("user", {}).get("login")
created_at = raw_comment.get("created_at")
self._created = (
datetime.datetime.fromisoformat(created_at.replace("Z", "+00:00"))
if created_at
else None
)
updated_at = raw_comment.get("updated_at")
self._edited = (
datetime.datetime.fromisoformat(updated_at.replace("Z", "+00:00"))
if updated_at
else None
)

@property
def body(self) -> str:
return self._raw_comment.get("body")

@body.setter
def body(self, new_body: str) -> None:
"""
Updates the comment body in Forgejo.
"""
url = (
f"{self._forgejo_client.forgejo_url}/api/v1/repos/"
f"{self._repository}/issues/{self._issue_number}/comments/{self._id}"
)
data = {"body": new_body}
self._forgejo_client._make_request("PATCH", url, data)
self._raw_comment["body"] = new_body

@property
def edited(self) -> datetime.datetime:
return self._edited

def get_reactions(self) -> list[Reaction]:
"""
Retrieves all reactions for this comment.
"""
url = (
f"{self._forgejo_client.forgejo_url}/api/v1/repos/"
f"{self._repository}/issues/{self._issue_number}/comments/{self._id}/reactions"
)
reactions_data = self._forgejo_client._make_request("GET", url)
return [
ForgejoReaction(
r,
self._forgejo_client,
self._repository,
self._issue_number,
)
for r in reactions_data
]

def add_reaction(self, reaction: str) -> ForgejoReaction:
"""
Adds a reaction to the comment.

Args:
reaction (str): The reaction content (e.g., "+1", "heart").

Returns:
ForgejoReaction: The created reaction object.
"""
url = (
f"{self._forgejo_client.forgejo_url}/api/v1/repos/"
f"{self._repository}/issues/{self._issue_number}/comments/{self._id}/reactions"
)
data = {"content": reaction}
reaction_data = self._forgejo_client._make_request("POST", url, data)
return ForgejoReaction(
reaction_data,
self._forgejo_client,
self._repository,
self._issue_number,
)

@property
def id(self) -> int:
return self._id

@property
def author(self) -> str:
return self._author

@property
def created(self) -> datetime.datetime:
return self._created


class ForgejoIssueComment(ForgejoComment, IssueComment):
def __str__(self) -> str:
return "Forgejo" + super().__str__()


class ForgejoPRComment(ForgejoComment, PRComment):
def __str__(self) -> str:
return "Forgejo" + super().__str__()


class ForgejoCommitComment(ForgejoComment, CommitComment):
def __str__(self) -> str:
return "Forgejo" + super().__str__()