diff --git a/aboutcode/federatedcode/client/__init__.py b/aboutcode/federatedcode/client/__init__.py index 3021ab4..8ba1405 100644 --- a/aboutcode/federatedcode/client/__init__.py +++ b/aboutcode/federatedcode/client/__init__.py @@ -9,6 +9,7 @@ import os from typing import Union +from urllib.parse import quote from urllib.parse import urljoin import requests @@ -60,3 +61,10 @@ def get_package_scan(purl: Union[PackageURL, str]): if response.status_code == 404: raise ScanNotAvailableError(f"No scan available for {purl!s}") raise err + + +def subscribe_package(federatedcode_host, remote_username, purl): + """Subscribe package for their metadata update from FederatedCode.""" + + url_path = f"api/v0/users/@{remote_username}/subscribe/?purl={quote(purl)}" + return requests.get(urljoin(federatedcode_host, url_path)) diff --git a/aboutcode/federatedcode/contrib/__init__.py b/aboutcode/federatedcode/contrib/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/aboutcode/federatedcode/contrib/django/__init__.py b/aboutcode/federatedcode/contrib/django/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/aboutcode/federatedcode/contrib/django/models.py b/aboutcode/federatedcode/contrib/django/models.py new file mode 100644 index 0000000..125b649 --- /dev/null +++ b/aboutcode/federatedcode/contrib/django/models.py @@ -0,0 +1,38 @@ +# +# Copyright (c) nexB Inc. and others. All rights reserved. +# FederatedCode is a trademark of nexB Inc. +# SPDX-License-Identifier: Apache-2.0 +# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. +# See https://github.com/nexB/federatedcode for support or download. +# See https://aboutcode.org for more information about AboutCode.org OSS projects. +# + +from django.db import models +from django.utils.translation import gettext_lazy as _ + + +class FederatedCodePackageActivityMixin(models.Model): + """Abstract Model for FederatedCode package activity.""" + + author = models.CharField( + max_length=300, + null=False, + blank=False, + help_text=_("Author of package activity."), + ) + + content = models.JSONField( + null=False, + blank=False, + help_text=_("Package activity content."), + ) + + activity_update_date = models.DateTimeField( + null=True, + blank=True, + db_index=True, + help_text=_("Timestamp indicating when original activity was last updated."), + ) + + class Meta: + abstract = True diff --git a/aboutcode/federatedcode/contrib/django/utils.py b/aboutcode/federatedcode/contrib/django/utils.py new file mode 100644 index 0000000..8846160 --- /dev/null +++ b/aboutcode/federatedcode/contrib/django/utils.py @@ -0,0 +1,35 @@ +# +# Copyright (c) nexB Inc. and others. All rights reserved. +# FederatedCode is a trademark of nexB Inc. +# SPDX-License-Identifier: Apache-2.0 +# See http://www.apache.org/licenses/LICENSE-2.0 for the license text. +# See https://github.com/nexB/federatedcode for support or download. +# See https://aboutcode.org for more information about AboutCode.org OSS projects. +# + + +import saneyaml + + +def get_package_activity_type(activity: dict) -> str: + return activity.get("type") + + +def get_package_activity_object(activity: dict) -> dict: + return activity.get("object") + + +def get_package_activity_author(activity: dict) -> str: + activity_object = get_package_activity_object(activity) + return activity_object.get("author") + + +def get_package_activity_content(activity: dict) -> dict: + activity_object = get_package_activity_object(activity) + content = activity_object.get("content") + return saneyaml.load(content) + + +def get_package_activity_update_date(activity: dict) -> str: + activity_object = get_package_activity_object(activity) + return activity_object.get("update_date") diff --git a/fedcode/management/commands/federate.py b/fedcode/management/commands/federate.py index 61b6ffe..bdef98f 100644 --- a/fedcode/management/commands/federate.py +++ b/fedcode/management/commands/federate.py @@ -7,6 +7,7 @@ # See https://aboutcode.org for more information about AboutCode.org OSS projects. # +import json from traceback import format_exc as traceback_format_exc import requests @@ -24,9 +25,8 @@ def send_fed_req_task(): if not rq.done: try: headers = {"Content-Type": "application/json"} - requests.post(rq.target, json=rq.body, headers=headers) + requests.post(rq.target, json=json.loads(rq.body), headers=headers) rq.done = True - rq.save() except Exception as e: rq.error_message = f"Failed to federate {rq!r} {e!r} \n {traceback_format_exc()}" finally: diff --git a/fedcode/models.py b/fedcode/models.py index 79a9b05..1803adb 100644 --- a/fedcode/models.py +++ b/fedcode/models.py @@ -8,6 +8,7 @@ # import uuid +from urllib.parse import urljoin from django.contrib.auth.models import User from django.contrib.contenttypes.fields import GenericForeignKey @@ -424,11 +425,13 @@ def absolute_url_ap(self): @property def inbox_url(self): if not self.local: - return self.remote_actor.url + return urljoin(self.remote_actor.url, "inbox") return full_reverse("user-inbox", self.user.username) @property def outbox_url(self): + if not self.local: + return urljoin(self.remote_actor.url, "outbox") return full_reverse("user-outbox", self.user.username) @property @@ -444,10 +447,11 @@ def key_id(self): @property def to_ap(self): + name = self.user.username if self.local else self.remote_actor.username return { "id": self.absolute_url_ap, "type": "Person", - "name": self.user.username, + "name": name, "summary": self.summary, "inbox": self.inbox_url, "outbox": self.outbox_url, diff --git a/fedcode/pipes/utils.py b/fedcode/pipes/utils.py index ee8cfdf..adc06c2 100644 --- a/fedcode/pipes/utils.py +++ b/fedcode/pipes/utils.py @@ -7,6 +7,8 @@ # See https://aboutcode.org for more information about AboutCode.org OSS projects. # +import json + import saneyaml from packageurl import PackageURL @@ -22,7 +24,7 @@ def create_note(pkg, note_dict): create_activity = CreateActivity(actor=pkg.to_ap, object=note.to_ap) Activity.federate( targets=pkg.followers_inboxes, - body=create_activity.to_ap(), + body=json.dumps(create_activity.to_ap()), key_id=pkg.key_id, ) @@ -36,7 +38,7 @@ def delete_note(pkg, note_dict): deleted_activity = DeleteActivity(actor=pkg.to_ap, object=note_ap) Activity.federate( targets=pkg.followers_inboxes, - body=deleted_activity.to_ap, + body=json.dumps(deleted_activity.to_ap), key_id=pkg.key_id, ) diff --git a/fedcode/views.py b/fedcode/views.py index d470455..ae904b5 100644 --- a/fedcode/views.py +++ b/fedcode/views.py @@ -698,22 +698,19 @@ def post(self, request, *args, **kwargs): return HttpResponseBadRequest("Invalid message") -@method_decorator(has_valid_header, name="dispatch") class RemoteUserSubscribe(View): def get(self, request, *args, **kwargs): - """Endpoint to for existing remote user to subscribe to package.""" + """Endpoint for existing remote user to subscribe to package.""" purl = request.GET.get("purl").rstrip("/") package = get_object_or_404(Package, purl=purl) remote_actor = get_object_or_404(RemoteActor, username=kwargs["username"]) - host = request.get_host() - if urlparse(remote_actor.url).netloc == host: - _, created = Follow.objects.get_or_create(package=package, person=remote_actor.person) - message = f"Already subscribed package {purl}" - if created: - message = f"Successfully subscribed package {purl}" - - return JsonResponse({"status": "success", "message": message}) - return HttpResponseBadRequest() + + _, created = Follow.objects.get_or_create(package=package, person=remote_actor.person) + message = f"Already subscribed to package {purl}" + if created: + message = f"Successfully subscribed to package {purl}" + + return JsonResponse({"status": "success", "message": message}) @method_decorator(has_valid_header, name="dispatch") diff --git a/federatedcode/urls.py b/federatedcode/urls.py index fb16091..5457cd9 100644 --- a/federatedcode/urls.py +++ b/federatedcode/urls.py @@ -94,7 +94,7 @@ path("api/v0/users/@/outbox", UserOutbox.as_view(), name="user-outbox"), path("api/v0/purls/@/inbox", PackageInbox.as_view(), name="purl-inbox"), path( - "api/v0/users/@/subscribe", + "api/v0/users/@/subscribe/", RemoteUserSubscribe.as_view(), name="purl-subscribe", ), diff --git a/pyproject-aboutcode.federatedcode.toml b/pyproject-aboutcode.federatedcode.toml index 29d212a..3d70bad 100644 --- a/pyproject-aboutcode.federatedcode.toml +++ b/pyproject-aboutcode.federatedcode.toml @@ -35,11 +35,13 @@ classifiers = [ ] dependencies = [ - "packageurl_python >= 0.15.6", "aboutcode.hashid>=0.2.0", - "python-dotenv>=1.0.1", "click>=8.1.7", + "Django>=5.1.2", + "packageurl_python >= 0.15.6", + "python-dotenv>=1.0.1", "requests>=2.32.3", + "saneyaml>=0.6.0", ] urls = { Homepage = "https://github.com/aboutcode-org/federatedcode" }