Boosts - handle url, store in database, send, notify.

This commit is contained in:
Adam Kelly 2020-03-30 15:13:32 +01:00
parent 84d7e7c394
commit 745ca7d4ff
11 changed files with 133 additions and 2 deletions

View file

@ -10,4 +10,5 @@ from .status import get_review, get_review_article
from .status import get_comment, get_comment_article
from .status import get_status, get_replies, get_replies_page
from .status import get_favorite, get_unfavorite
from .status import get_boost
from .status import get_add_tag, get_remove_tag

View file

@ -158,6 +158,17 @@ def get_unfavorite(favorite):
}
def get_boost(boost):
''' boost/announce a post '''
return {
'@context': 'https://www.w3.org/ns/activitystreams',
'id': boost.absolute_id,
'type': 'Announce',
'actor': boost.user.actor,
'object': boost.boosted_status.absolute_id,
}
def get_add_tag(tag):
''' add activity for tagging a book '''
uuid = uuid4()

View file

@ -38,6 +38,7 @@ def shared_inbox(request):
'Reject': handle_follow_reject,
'Create': handle_create,
'Like': handle_favorite,
'Announce': handle_boost,
'Add': {
'Tag': handle_add,
},
@ -286,6 +287,27 @@ def handle_unfavorite(activity):
return HttpResponse()
def handle_boost(activity):
''' someone gave us a boost! '''
try:
status_id = activity['object'].split('/')[-1]
status = models.Status.objects.get(id=status_id)
booster = get_or_create_remote_user(activity['actor'])
except (models.Status.DoesNotExist, models.User.DoesNotExist):
return HttpResponseNotFound()
if not booster.local:
status_builder.create_boost_from_activity(booster, activity)
status_builder.create_notification(
status.user,
'BOOST',
related_user=booster,
related_status=status,
)
return HttpResponse()
def handle_add(activity):
''' someone is tagging or shelving a book '''
if activity['object']['type'] == 'Tag':

View file

@ -0,0 +1,42 @@
# Generated by Django 3.0.3 on 2020-03-30 14:56
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('fedireads', '0025_auto_20200330_0037'),
]
operations = [
migrations.CreateModel(
name='Boost',
fields=[
('status_ptr', models.OneToOneField(auto_created=True, on_delete=django.db.models.deletion.CASCADE, parent_link=True, primary_key=True, serialize=False, to='fedireads.Status')),
],
options={
'abstract': False,
},
bases=('fedireads.status',),
),
migrations.RemoveConstraint(
model_name='notification',
name='notification_type_valid',
),
migrations.AlterField(
model_name='notification',
name='notification_type',
field=models.CharField(choices=[('FAVORITE', 'Favorite'), ('REPLY', 'Reply'), ('TAG', 'Tag'), ('FOLLOW', 'Follow'), ('FOLLOW_REQUEST', 'Follow Request'), ('BOOST', 'Boost')], max_length=255),
),
migrations.AddConstraint(
model_name='notification',
constraint=models.CheckConstraint(check=models.Q(notification_type__in=['FAVORITE', 'REPLY', 'TAG', 'FOLLOW', 'FOLLOW_REQUEST', 'BOOST']), name='notification_type_valid'),
),
migrations.AddField(
model_name='boost',
name='boosted_status',
field=models.ForeignKey(on_delete=django.db.models.deletion.PROTECT, related_name='boosters', to='fedireads.Status'),
),
]

View file

@ -1,6 +1,6 @@
''' bring all the models into the app namespace '''
from .book import Connector, Book, Work, Edition, Author
from .shelf import Shelf, ShelfBook
from .status import Status, Review, Comment, Favorite, Tag, Notification
from .status import Status, Review, Comment, Favorite, Boost, Tag, Notification
from .user import User, UserFollows, UserFollowRequest, UserBlocks
from .user import FederatedServer

View file

@ -89,6 +89,21 @@ class Favorite(FedireadsModel):
unique_together = ('user', 'status')
class Boost(Status):
''' boost'ing a post '''
boosted_status = models.ForeignKey(
'Status',
on_delete=models.PROTECT,
related_name="boosters")
def save(self, *args, **kwargs):
self.status_type = 'Boost'
self.activity_type = 'Announce'
super().save(*args, **kwargs)
# This constraint can't work as it would cross tables.
# class Meta:
# unique_together = ('user', 'boosted_status')
class Tag(FedireadsModel):
''' freeform tags for books '''
user = models.ForeignKey('User', on_delete=models.PROTECT)
@ -107,7 +122,7 @@ class Tag(FedireadsModel):
NotificationType = models.TextChoices(
'NotificationType', 'FAVORITE REPLY TAG FOLLOW FOLLOW_REQUEST')
'NotificationType', 'FAVORITE REPLY TAG FOLLOW FOLLOW_REQUEST BOOST')
class Notification(FedireadsModel):
''' you've been tagged, liked, followed, etc '''

View file

@ -291,6 +291,22 @@ def handle_unfavorite(user, status):
recipients = get_recipients(user, 'direct', [status.user])
broadcast(user, fav_activity, recipients)
def handle_boost(user, status):
''' a user wishes to boost a status '''
try:
boost = models.Boost.objects.create(
boosted_status=status,
user=user,
)
boost.save()
except IntegrityError:
# you already boosted that
# TODO - doesn't work because unique constraint isn't enforcable.
return
boost_activity = activitypub.get_boost(boost)
recipients = get_recipients(user, 'public')
broadcast(user, boost_activity, recipients)
def handle_update_book(user, book):
''' broadcast the news about our book '''

View file

@ -102,6 +102,20 @@ def create_favorite_from_activity(user, activity):
return models.Favorite.objects.get(status=status, user=user)
def create_boost_from_activity(user, activity):
''' create a new boost activity '''
status = get_status(activity['object'])
remote_id = activity['id']
try:
return models.Boost.objects.create(
status=status,
user=user,
remote_id=remote_id,
)
except IntegrityError:
return models.Boost.objects.get(status=status, user=user)
def get_status(absolute_id):
''' find a status in the database '''
return get_by_absolute_id(absolute_id, models.Status)

View file

@ -32,6 +32,9 @@
<div class="row shrink">
{% include 'snippets/follow_request_buttons.html' with user=notification.related_user %}
</div>
{% elif notification.notification_type == 'BOOST' %}
boosted your <a href="{{ notification.related_status.absolute_id}}">status</a>
{% endif %}
</div>
{% endfor %}

View file

@ -85,6 +85,7 @@ urlpatterns = [
re_path(r'^favorite/(?P<status_id>\d+)/?$', actions.favorite),
re_path(r'^unfavorite/(?P<status_id>\d+)/?$', actions.unfavorite),
re_path(r'^boost/(?P<status_id>\d+)/?$', actions.boost),
re_path(r'^shelve/?$', actions.shelve),

View file

@ -240,6 +240,12 @@ def unfavorite(request, status_id):
outgoing.handle_unfavorite(request.user, status)
return redirect(request.headers.get('Referer', '/'))
@login_required
def boost(request, status_id):
''' boost a status '''
status = models.Status.objects.get(id=status_id)
outgoing.handle_boost(request.user, status)
return redirect(request.headers.get('Referer', '/'))
@login_required
def follow(request):