diff --git a/fedireads/activitypub_templates.py b/fedireads/activitypub_templates.py index 8887e6f1d..cb799d2fb 100644 --- a/fedireads/activitypub_templates.py +++ b/fedireads/activitypub_templates.py @@ -1,4 +1,7 @@ ''' generates activitypub formatted objects ''' +from uuid import uuid4 +from fedireads.settings import DOMAIN + def shelve_action(user, book, shelf): ''' a user puts a book on a shelf. @@ -27,3 +30,15 @@ def shelve_action(user, book, shelf): } } + +def accept_follow(activity, user): + ''' say YES! to a user ''' + uuid = uuid4() + return { + '@context': 'https://www.w3.org/ns/activitystreams', + 'id': 'https://%s/%s' % (DOMAIN, uuid), + 'type': 'Accept', + 'actor': user.actor['id'], + 'object': activity, + } + diff --git a/fedireads/federation.py b/fedireads/federation.py index e2d92708d..782e526e8 100644 --- a/fedireads/federation.py +++ b/fedireads/federation.py @@ -10,6 +10,8 @@ from django.views.decorators.csrf import csrf_exempt from fedireads.settings import DOMAIN from fedireads import models from fedireads import openlibrary +import fedireads.activitypub_templates as templates +import json import requests def webfinger(request): @@ -52,13 +54,18 @@ def inbox(request, username): # return a collection of something? pass - activity = request.POST.dict() + activity = json.loads(request.body) if activity['type'] == 'Add': handle_add(activity) + if activity['type'] == 'Follow': + response = handle_follow(activity) + return JsonResponse(response) + return HttpResponse() def handle_add(activity): + ''' adding a book to a shelf ''' book_id = activity['object']['url'] book = openlibrary.get_or_create_book(book_id) user_ap_id = activity['actor'].replace('https//:', '') @@ -70,6 +77,31 @@ def handle_add(activity): added_by=user, ).save() + +def handle_follow(activity): + ''' + { + "@context": "https://www.w3.org/ns/activitystreams", + "id": "https://friend.camp/768222ce-a1c7-479c-a544-c93b8b67fb54", + "type": "Follow", + "actor": "https://friend.camp/users/tripofmice", + "object": "https://ff2cb3e9.ngrok.io/api/u/mouse" + } + ''' + # figure out who they want to follow + following = activity['object'].replace('https://%s/api/u/' % DOMAIN, '') + following = models.User.objects.get(username=following) + # figure out who they are + ap_id = activity['actor'] + try: + user = models.User.objects.get(activitypub_id=ap_id) + except models.User.DoesNotExist: + user = models.User(activitypub_id=ap_id, local=False).save() + following.followers.add(user) + # accept the request + return templates.accept_follow(activity, following) + + @csrf_exempt def outbox(request, username): user = models.User.objects.get(username=username) @@ -106,19 +138,16 @@ date: %s''' % (inbox_fragment, DOMAIN, now) signature += 'signature="%s"' % b64encode(signed_message) response = requests.post( recipient, - data=action, + body=action, headers={ 'Date': now, 'Signature': signature, 'Host': DOMAIN, - 'Content-Type': 'application/json', }, ) if not response.ok: return response.raise_for_status() -def handle_follow(data): - pass def get_or_create_remote_user(activity): pass diff --git a/fedireads/migrations/0001_initial.py b/fedireads/migrations/0001_initial.py index 48ca9c1ff..f3caea47c 100644 --- a/fedireads/migrations/0001_initial.py +++ b/fedireads/migrations/0001_initial.py @@ -1,4 +1,4 @@ -# Generated by Django 2.0.13 on 2020-01-27 01:47 +# Generated by Django 2.0.13 on 2020-01-27 02:41 from django.conf import settings import django.contrib.auth.models @@ -34,9 +34,9 @@ class Migration(migrations.Migration): ('date_joined', models.DateTimeField(default=django.utils.timezone.now, verbose_name='date joined')), ('activitypub_id', models.CharField(max_length=255)), ('private_key', models.TextField(blank=True, null=True)), - ('public_key', models.TextField()), + ('public_key', models.TextField(blank=True, null=True)), ('api_key', models.CharField(blank=True, max_length=255, null=True)), - ('actor', django.contrib.postgres.fields.jsonb.JSONField()), + ('actor', django.contrib.postgres.fields.jsonb.JSONField(blank=True, null=True)), ('local', models.BooleanField(default=True)), ('created_date', models.DateTimeField(auto_now_add=True)), ('updated_date', models.DateTimeField(auto_now=True)), diff --git a/fedireads/models.py b/fedireads/models.py index 8df1ea0a6..9c474f9c9 100644 --- a/fedireads/models.py +++ b/fedireads/models.py @@ -12,9 +12,9 @@ class User(AbstractUser): ''' a user who wants to read books ''' activitypub_id = models.CharField(max_length=255) private_key = models.TextField(blank=True, null=True) - public_key = models.TextField() + public_key = models.TextField(blank=True, null=True) api_key = models.CharField(max_length=255, blank=True, null=True) - actor = JSONField() + actor = JSONField(blank=True, null=True) local = models.BooleanField(default=True) created_date = models.DateTimeField(auto_now_add=True) updated_date = models.DateTimeField(auto_now=True) diff --git a/fedireads/templates/user.html b/fedireads/templates/user.html index 09c6dbec6..9c4413f3f 100644 --- a/fedireads/templates/user.html +++ b/fedireads/templates/user.html @@ -3,7 +3,7 @@
-

{{ user.username }}

+

{{ user.username }}

Since {{ user.created_date }}

{% if not is_self %} {% if not following %} @@ -19,11 +19,22 @@ {% endif %} {% endif %}
- {% for book in books.all %} -
- {{ book.data.title }} by {{ book.authors.first.data.name }} + +
+

Books

+ {% for book in books.all %} +
+ {{ book.data.title }} by {{ book.authors.first.data.name }} +
+ {% endfor %} +
+ +
+

Followers

+ {% for follower in user.followers.all %} + {{ follower.activitypub_id }} + {% endfor %}
- {% endfor %}
{% endblock %} diff --git a/fedireads/views.py b/fedireads/views.py index bbf7c8f81..26fdbc1b2 100644 --- a/fedireads/views.py +++ b/fedireads/views.py @@ -57,12 +57,10 @@ def user_profile(request, username): ''' profile page for a user ''' user = models.User.objects.get(username=username) books = models.Book.objects.filter(shelves__user=user) - following = user.followers.filter(id=request.user.id).count() > 0 data = { 'user': user, 'books': books, 'is_self': request.user.id == user.id, - 'following': following, } return TemplateResponse(request, 'user.html', data)