Skip to content

Commit 789b002

Browse files
authored
Merge pull request #49 from aboutcode-org/39-metadata-subscribe
Add endpoint to subscribe to package activity
2 parents 751aa89 + 1b8da54 commit 789b002

File tree

10 files changed

+118
-20
lines changed

10 files changed

+118
-20
lines changed

Makefile

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -52,11 +52,11 @@ envfile:
5252

5353
isort:
5454
@echo "-> Apply isort changes to ensure proper imports ordering"
55-
@${ACTIVATE} isort --profile black .
55+
@${ACTIVATE} isort --profile black aboutcode/ fedcode/ federatedcode/ tests/
5656

5757
black:
5858
@echo "-> Apply black code formatter"
59-
@${ACTIVATE} black ${BLACK_ARGS} .
59+
@${ACTIVATE} black ${BLACK_ARGS} aboutcode/ fedcode/ federatedcode/ tests/
6060

6161
doc8:
6262
@echo "-> Run doc8 validation"
@@ -92,7 +92,7 @@ migrate:
9292

9393
test:
9494
@echo "-> Run the test suite"
95-
@${ACTIVATE} pytest -vvs
95+
@${ACTIVATE} pytest -vvs tests/ fedcode/ federatedcode/ aboutcode/
9696

9797
docs:
9898
rm -rf docs/_build/

fedcode/management/commands/federate.py

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99

1010
from traceback import format_exc as traceback_format_exc
1111

12+
import requests
1213
from django.core.management.base import BaseCommand
1314

1415
from fedcode.models import FederateRequest
@@ -17,19 +18,17 @@
1718

1819

1920
def send_fed_req_task():
20-
"""
21-
send_fed_req_task is a task to send the http signed request to the target and save the status of the request
22-
"""
21+
"""Send activity request to the target and save the status."""
22+
2323
for rq in FederateRequest.objects.all().order_by("created_at"):
2424
if not rq.done:
2525
try:
26-
HttpSignature.signed_request(
27-
rq.target, rq.body, FEDERATEDCODE_PRIVATE_KEY, rq.key_id
28-
)
26+
headers = {"Content-Type": "application/json"}
27+
requests.post(rq.target, json=rq.body, headers=headers)
2928
rq.done = True
3029
rq.save()
3130
except Exception as e:
32-
rq.error_message = e
31+
rq.error_message = f"Failed to federate {rq!r} {e!r} \n {traceback_format_exc()}"
3332
finally:
3433
rq.save()
3534

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# Generated by Django 5.0.1 on 2024-12-10 10:01
2+
3+
import django.db.models.deletion
4+
from django.conf import settings
5+
from django.db import migrations, models
6+
7+
8+
class Migration(migrations.Migration):
9+
dependencies = [
10+
("fedcode", "0002_alter_package_options_alter_federaterequest_done_and_more"),
11+
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
12+
]
13+
14+
operations = [
15+
migrations.RemoveField(
16+
model_name="package",
17+
name="local",
18+
),
19+
migrations.RemoveField(
20+
model_name="person",
21+
name="local",
22+
),
23+
migrations.AlterField(
24+
model_name="person",
25+
name="user",
26+
field=models.OneToOneField(
27+
blank=True,
28+
null=True,
29+
on_delete=django.db.models.deletion.CASCADE,
30+
to=settings.AUTH_USER_MODEL,
31+
),
32+
),
33+
migrations.AddConstraint(
34+
model_name="person",
35+
constraint=models.CheckConstraint(
36+
check=models.Q(
37+
models.Q(("remote_actor__isnull", True), ("user__isnull", False)),
38+
models.Q(("remote_actor__isnull", False), ("user__isnull", True)),
39+
_connector="OR",
40+
),
41+
name="either_local_or_remote",
42+
),
43+
),
44+
]

fedcode/models.py

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,6 @@ class Actor(models.Model):
6464
blank=False,
6565
)
6666

67-
local = models.BooleanField(
68-
default=True,
69-
)
70-
7167
class Meta:
7268
abstract = True
7369

@@ -237,6 +233,7 @@ def to_ap(self):
237233
"type": "Note",
238234
"author": self.acct,
239235
"content": self.content,
236+
"update_date": str(self.updated_at),
240237
}
241238

242239

@@ -365,6 +362,7 @@ class Person(Actor):
365362
user = models.OneToOneField(
366363
User,
367364
null=True,
365+
blank=True,
368366
on_delete=models.CASCADE,
369367
)
370368

@@ -381,6 +379,21 @@ class Person(Actor):
381379
help_text="Notes created by this user",
382380
)
383381

382+
class Meta:
383+
constraints = [
384+
models.CheckConstraint(
385+
check=(
386+
models.Q(user__isnull=False, remote_actor__isnull=True)
387+
| models.Q(user__isnull=True, remote_actor__isnull=False)
388+
),
389+
name="either_local_or_remote",
390+
),
391+
]
392+
393+
@property
394+
def local(self):
395+
return bool(self.user)
396+
384397
@property
385398
def avatar_absolute_url(self):
386399
return f'{"https://"}{FEDERATEDCODE_DOMAIN}{self.avatar.url}'
@@ -410,6 +423,8 @@ def absolute_url_ap(self):
410423

411424
@property
412425
def inbox_url(self):
426+
if not self.local:
427+
return self.remote_actor.url
413428
return full_reverse("user-inbox", self.user.username)
414429

415430
@property
@@ -477,7 +492,10 @@ class Meta:
477492
ordering = ["-updated_at"]
478493

479494
def __str__(self):
480-
return f"{self.person.user.username} - {self.package.purl}"
495+
username = self.person.remote_actor.username
496+
if self.person.local:
497+
username = self.person.user.username
498+
return f"{username} - {self.package.purl}"
481499

482500

483501
class Repository(models.Model):

fedcode/templates/pkg_profile.html

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,12 @@
8282
<figure class="image is-64x64 mb-5 mr-5 ml-5 mt-5">
8383
<img class="is-rounded" src="{{ follower.person.avatar.url }}" alt="{{ follower.person.user.username }} profile image">
8484
<p class="is-size-5 has-text-centered">
85-
<a href="{% url 'user-profile' follower.person.user.username %}">@{{ follower.person.user.username }}</a>
86-
</p>
85+
{% if follower.person.local %}
86+
<a href="{% url 'user-profile' follower.person.user.username %}">@{{ follower.person.user.username }}</a>
87+
{% else %}
88+
<a href="{% url 'user-profile' follower.person.remote_actor.username %}">@{{ follower.person.remote_actor.username }}</a>
89+
{% endif %}
90+
</p>
8791
</figure>
8892
</div>
8993
{% endfor %}

fedcode/views.py

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import json
1111
import logging
1212
import os.path
13+
from urllib.parse import urlparse
1314

1415
import requests
1516
from django.contrib import messages
@@ -59,6 +60,7 @@
5960
from fedcode.models import Note
6061
from fedcode.models import Package
6162
from fedcode.models import Person
63+
from fedcode.models import RemoteActor
6264
from fedcode.models import Repository
6365
from fedcode.models import Reputation
6466
from fedcode.models import Review
@@ -696,6 +698,24 @@ def post(self, request, *args, **kwargs):
696698
return HttpResponseBadRequest("Invalid message")
697699

698700

701+
@method_decorator(has_valid_header, name="dispatch")
702+
class RemoteUserSubscribe(View):
703+
def get(self, request, *args, **kwargs):
704+
"""Endpoint to for existing remote user to subscribe to package."""
705+
purl = request.GET.get("purl").rstrip("/")
706+
package = get_object_or_404(Package, purl=purl)
707+
remote_actor = get_object_or_404(RemoteActor, username=kwargs["username"])
708+
host = request.get_host()
709+
if urlparse(remote_actor.url).netloc == host:
710+
_, created = Follow.objects.get_or_create(package=package, person=remote_actor.person)
711+
message = f"Already subscribed package {purl}"
712+
if created:
713+
message = f"Successfully subscribed package {purl}"
714+
715+
return JsonResponse({"status": "success", "message": message})
716+
return HttpResponseBadRequest()
717+
718+
699719
@method_decorator(has_valid_header, name="dispatch")
700720
class PackageInbox(View):
701721
def get(self, request, *args, **kwargs):
@@ -736,7 +756,7 @@ def get(self, request, *args, **kwargs):
736756
(or at least the ones you're authorized to see).
737757
(client-to-server and/or server-to-server)"""
738758

739-
actor = Package.objects.get(purl=kwargs["purl_string"])
759+
actor = get_object_or_404(Package, purl=kwargs["purl_string"])
740760
return JsonResponse(
741761
{"notes": ap_collection(actor.notes)},
742762
content_type=AP_CONTENT_TYPE,

federatedcode/urls.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@
66
# See https://github.com/nexB/federatedcode for support or download.
77
# See https://aboutcode.org for more information about AboutCode.org OSS projects.
88
#
9+
910
from django.conf import settings
10-
from django.conf.urls.static import static
1111
from django.contrib import admin
1212
from django.urls import include
1313
from django.urls import path
@@ -28,6 +28,7 @@
2828
from fedcode.views import PersonSignUp
2929
from fedcode.views import PersonUpdateView
3030
from fedcode.views import PersonView
31+
from fedcode.views import RemoteUserSubscribe
3132
from fedcode.views import RepositoryListView
3233
from fedcode.views import ReviewListView
3334
from fedcode.views import ReviewView
@@ -92,6 +93,11 @@
9293
path("api/v0/users/@<str:username>/inbox", UserInbox.as_view(), name="user-inbox"),
9394
path("api/v0/users/@<str:username>/outbox", UserOutbox.as_view(), name="user-outbox"),
9495
path("api/v0/purls/@<path:purl_string>/inbox", PackageInbox.as_view(), name="purl-inbox"),
96+
path(
97+
"api/v0/users/@<str:username>/subscribe",
98+
RemoteUserSubscribe.as_view(),
99+
name="purl-subscribe",
100+
),
95101
path(
96102
"api/v0/purls/@<path:purl_string>/outbox",
97103
PackageOutbox.as_view(),

pyproject-aboutcode.federatedcode.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ classifiers = [
3636

3737
dependencies = [
3838
"packageurl_python >= 0.15.6",
39-
"aboutcode.hashid>=0.1.0",
39+
"aboutcode.hashid>=0.2.0",
4040
"python-dotenv>=1.0.1",
4141
"click>=8.1.7",
4242
"requests>=2.32.3",

tests/test_ap_api.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,12 +209,14 @@ def test_get_user_inbox(person, vulnerability, review, package):
209209
"type": "Note",
210210
"author": "pkg:maven/org.apache.logging@127.0.0.1:8000",
211211
"content": "yaml data1",
212+
"update_date": str(note1.updated_at),
212213
},
213214
{
214215
"id": f"https://127.0.0.1:8000/notes/{note2.id}",
215216
"type": "Note",
216217
"author": "pkg:maven/org.apache.logging@127.0.0.1:8000",
217218
"content": "yaml data2",
219+
"update_date": str(note2.updated_at),
218220
},
219221
],
220222
},
@@ -282,6 +284,7 @@ def test_get_user_outbox(person, vulnerability, review, note):
282284
"content": note.content,
283285
"id": f"https://127.0.0.1:8000/notes/{note.id}",
284286
"type": "Note",
287+
"update_date": str(note.updated_at),
285288
}
286289
],
287290
},
@@ -383,6 +386,7 @@ def test_get_package_inbox(package, service):
383386
"[] fixing_vulnerabilities: []",
384387
"id": f"https://127.0.0.1:8000/notes/{note1.id}",
385388
"type": "Note",
389+
"update_date": str(note1.updated_at),
386390
}
387391
],
388392
"totalItems": 1,
@@ -413,6 +417,7 @@ def test_get_package_outbox(service, package):
413417
"content": "yaml data1",
414418
"id": f"https://127.0.0.1:8000/notes/{note1.id}",
415419
"type": "Note",
420+
"update_date": str(note1.updated_at),
416421
}
417422
],
418423
"totalItems": 1,

tests/test_vocabulary_toap.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,7 @@ def test_objects_to_ap(repo, review, vulnerability, note, rep, mute_post_save_si
158158
"id": f"https://127.0.0.1:8000/notes/{note.id}",
159159
"author": note.acct,
160160
"content": note.content,
161+
"update_date": str(note.updated_at),
161162
}
162163

163164
assert rep.to_ap == {
@@ -168,5 +169,6 @@ def test_objects_to_ap(repo, review, vulnerability, note, rep, mute_post_save_si
168169
"id": f"https://127.0.0.1:8000/notes/{note.id}",
169170
"author": note.acct,
170171
"content": note.content,
172+
"update_date": str(note.updated_at),
171173
},
172174
}

0 commit comments

Comments
 (0)