diff --git a/activitypub/models.py b/activitypub/models.py index f9c5ab9..3a25205 100644 --- a/activitypub/models.py +++ b/activitypub/models.py @@ -1,5 +1,7 @@ +import json + from django.db.models import Model, ForeignKey, CharField, TextField, BooleanField -from django.db.models import ManyToManyField +from django.db.models import BinaryField, DateField, ManyToManyField from django.db.models.signals import post_save from django.dispatch import receiver @@ -79,8 +81,33 @@ class Note(Model): "actor": self.person.uris.id, } +class Activity(Model): + + ap_id = TextField() + payload = BinaryField() + created_at = DateField(auto_now_add=True) + person = ForeignKey(Person, related_name='activities') + remote = BooleanField(default=False) + + @property + def uris(self): + if self.remote: + ap_id = self.ap_id + else: + ap_id = uri("activity", self.person.username, self.id) + return URIs(id=ap_id) + + def to_activitystream(self): + payload = self.payload.decode("utf-8") + data = json.loads(payload) + data.update({ + "id": self.uris.id + }) + return data + @receiver(post_save, sender=Person) @receiver(post_save, sender=Note) +@receiver(post_save, sender=Activity) def save_ap_id(sender, instance, created, **kwargs): if created and not instance.remote: instance.ap_id = instance.uris.id diff --git a/activitypub/urls.py b/activitypub/urls.py index be32caa..6ee9cc3 100644 --- a/activitypub/urls.py +++ b/activitypub/urls.py @@ -2,7 +2,7 @@ from django.conf.urls import url from django.contrib import admin from activitypub.views import person, note, notes, inbox, outbox -from activitypub.views import followers, following +from activitypub.views import followers, following, activity urlpatterns = [ url(r'^@(\w+)/notes/(\w+)', note, name="note"), @@ -10,6 +10,7 @@ urlpatterns = [ url(r'^@(\w+)/following', following, name="following"), url(r'^@(\w+)/followers', followers, name="followers"), url(r'^@(\w+)/inbox', inbox, name="inbox"), + url(r'^@(\w+)/outbox/(\w+)', activity, name="activity"), url(r'^@(\w+)/outbox', outbox, name="outbox"), url(r'^@([^/]+)$', person, name="person"), url(r'^@([^/]+)/notes', notes), diff --git a/activitypub/views.py b/activitypub/views.py index 1554a5c..572036a 100644 --- a/activitypub/views.py +++ b/activitypub/views.py @@ -8,7 +8,7 @@ from django.urls import reverse from django.shortcuts import get_object_or_404, render from django.views.decorators.csrf import csrf_exempt -from activitypub.models import Person, Note +from activitypub.models import Person, Note, Activity from activitypub import activities from activitypub.activities import as_activitystream @@ -25,12 +25,15 @@ def note(request, username, note_id): @csrf_exempt def outbox(request, username): - if request.method != "POST": - return HttpResponseNotAllowed(["POST"]) + person = get_object_or_404(Person, username=username) + + if request.method == "GET": + objects = person.activities.filter(remote=False).order_by('-created_at') + collection = activities.OrderedCollection(objects) + return JsonResponse(collection.to_json(context=True)) payload = request.body.decode("utf-8") activity = json.loads(payload, object_hook=as_activitystream) - person = get_object_or_404(Person, username=username) if activity.type == "Note": obj = activity @@ -52,7 +55,9 @@ def outbox(request, username): # TODO: check for actor being the right actor object activity.object.id = note.uris.id + activity.id = store(activity, person) deliver(activity) + return HttpResponseRedirect(note.uris.id) if activity.type == "Follow": @@ -64,16 +69,24 @@ def outbox(request, username): activity.actor = person.uris.id activity.to = followed.uris.id + activity.id = store(activity, person) deliver(activity) return HttpResponse() # TODO: code 202 raise Exception("Invalid Request") +def store(activity, person, remote=False): + payload = bytes(json.dumps(activity.to_json()), "utf-8") + obj = Activity(payload=payload, person=person, remote=remote) + if remote: + obj.ap_id = activity.id + obj.save() + return obj.ap_id + def deliver(activity): audience = activity.get_audience() activity = activity.strip_audience() audience = get_final_audience(audience) - print("audience", audience) for ap_id in audience: deliver_to(ap_id, activity) @@ -125,18 +138,21 @@ def get_or_create_remote_person(ap_id): @csrf_exempt def inbox(request, username): person = get_object_or_404(Person, username=username) - if request.method != "POST": - return HttpResponseNotAllowed(["POST"]) + if request.method == "GET": + objects = person.activities.filter(remote=True).order_by('-created_at') + collection = activities.OrderedCollection(objects) + return JsonResponse(collection.to_json(context=True)) payload = request.body.decode("utf-8") activity = json.loads(payload, object_hook=as_activitystream) activity.validate() - print(activity) if activity.type == "Create": handle_note(activity) elif activity.type == "Follow": handle_follow(activity) + + store(activity, person, remote=True) return HttpResponse() def handle_note(activity): @@ -192,3 +208,9 @@ def following(request, username): person = get_object_or_404(Person, username=username) following = activities.OrderedCollection(person.following.all()) return JsonResponse(following.to_json(context=True)) + +def activity(request, username, aid): + activity = get_object_or_404(Activity, pk=aid) + payload = activity.payload.decode("utf-8") + activity = json.loads(payload, object_hook=as_activitystream) + return JsonResponse(activity.to_json(context=True))