Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Federate pinned entries #831

Merged
merged 21 commits into from
Jul 4, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
1b96be2
Federate pinned entries
BentiGorlich Jun 15, 2024
8e7f60e
Fix missing url params
BentiGorlich Jun 15, 2024
d773bac
Implement incoming add/remove activities
BentiGorlich Jun 16, 2024
e397c4b
Merge branch 'main' into new/federate-pinned-entries
BentiGorlich Jun 18, 2024
c8fb494
Fix moderator federation
BentiGorlich Jun 26, 2024
b4b70f6
Fix some fields not being urls
BentiGorlich Jun 26, 2024
bfe682e
Implement announce
BentiGorlich Jun 26, 2024
a3b7fd2
Merge branch 'main' into new/federate-pinned-entries
BentiGorlich Jun 26, 2024
56c9bd6
Merge branch 'main' into new/federate-pinned-entries
BentiGorlich Jun 26, 2024
5e1a43b
Merge branch 'main' into new/federate-pinned-entries
BentiGorlich Jun 29, 2024
00621cb
Merge branch 'main' into new/federate-pinned-entries
melroy89 Jun 29, 2024
4e48a29
Fix sticky on create, add modlog
BentiGorlich Jun 29, 2024
1219a0c
Merge branch 'main' into new/federate-pinned-entries
BentiGorlich Jun 30, 2024
6a3ee21
Merge branch 'main' into new/federate-pinned-entries
Jun 30, 2024
9d5fdcd
Merge branch 'main' into new/federate-pinned-entries
Jun 30, 2024
aef606c
Invalidate cache when an entry or post is pinned, so the next actor u…
BentiGorlich Jul 1, 2024
bee00e4
Merge branch 'main' into new/federate-pinned-entries
Jul 1, 2024
3f45460
Add method description to `pin`
BentiGorlich Jul 2, 2024
078903e
Merge branch 'main' into new/federate-pinned-entries
BentiGorlich Jul 2, 2024
8235392
Merge branch 'main' into new/federate-pinned-entries
Jul 2, 2024
6b30d15
Merge branch 'main' into new/federate-pinned-entries
melroy89 Jul 4, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions config/kbin_routes/activity_pub.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,12 @@ ap_magazine_moderators:
methods: [GET]
condition: '%kbin_ap_route_condition%'

ap_magazine_pinned:
controller: App\Controller\ActivityPub\Magazine\MagazinePinnedController
path: /m/{name}/pinned
methods: [GET]
condition: '%kbin_ap_route_condition%'

ap_entry:
controller: App\Controller\ActivityPub\EntryController
defaults: { slug: -, sortBy: hot }
Expand Down
28 changes: 28 additions & 0 deletions migrations/Version20240615225744.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

namespace DoctrineMigrations;

use Doctrine\DBAL\Schema\Schema;
use Doctrine\Migrations\AbstractMigration;

final class Version20240615225744 extends AbstractMigration
{
public function getDescription(): string
{
return 'Add a field to save the featured collection of magazines and users';
}

public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE magazine ADD ap_featured_url VARCHAR(255) DEFAULT NULL');
$this->addSql('ALTER TABLE "user" ADD ap_featured_url VARCHAR(255) DEFAULT NULL');
}

public function down(Schema $schema): void
{
$this->addSql('ALTER TABLE "user" DROP ap_featured_url');
$this->addSql('ALTER TABLE magazine DROP ap_featured_url');
}
}
60 changes: 60 additions & 0 deletions src/Controller/ActivityPub/Magazine/MagazinePinnedController.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<?php

declare(strict_types=1);

namespace App\Controller\ActivityPub\Magazine;

use App\Entity\Contracts\ActivityPubActivityInterface;
use App\Entity\Magazine;
use App\Factory\ActivityPub\EntryPageFactory;
use App\Repository\EntryRepository;
use App\Repository\TagLinkRepository;
use JetBrains\PhpStorm\ArrayShape;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Routing\Generator\UrlGeneratorInterface;

class MagazinePinnedController
{
public function __construct(
private readonly EntryRepository $entryRepository,
private readonly UrlGeneratorInterface $urlGenerator,
private readonly EntryPageFactory $entryFactory,
private readonly TagLinkRepository $tagLinkRepository,
) {
}

public function __invoke(Magazine $magazine, Request $request): JsonResponse
{
$data = $this->getCollectionItems($magazine);
$response = new JsonResponse($data);
$response->headers->set('Content-Type', 'application/activity+json');

return $response;
}

#[ArrayShape([
'@context' => 'array',
'type' => 'string',
'id' => 'string',
'totalItems' => 'int',
'orderedItems' => 'array',
])]
private function getCollectionItems(Magazine $magazine): array
{
$pinned = $this->entryRepository->findPinned($magazine);

$items = [];
foreach ($pinned as $entry) {
$items[] = $this->entryFactory->create($entry, $this->tagLinkRepository->getTagsOfEntry($entry));
}

return [
'@context' => [ActivityPubActivityInterface::CONTEXT_URL],
'type' => 'OrderedCollection',
'id' => $this->urlGenerator->generate('ap_magazine_pinned', ['name' => $magazine->name], UrlGeneratorInterface::ABSOLUTE_URL),
'totalItems' => \sizeof($items),
'orderedItems' => $items,
];
}
}
2 changes: 1 addition & 1 deletion src/Controller/Api/Entry/Moderate/EntriesPinApi.php
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ public function __invoke(
): JsonResponse {
$headers = $this->rateLimit($apiModerateLimiter);

$manager->pin($entry);
$manager->pin($entry, $this->getUserOrThrow());

return new JsonResponse(
$this->serializeEntry($factory->createDto($entry), $this->tagLinkRepository->getTagsOfEntry($entry)),
Expand Down
2 changes: 1 addition & 1 deletion src/Controller/Entry/EntryPinController.php
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public function __invoke(
): Response {
$this->validateCsrf('entry_pin', $request->request->get('token'));

$entry = $this->manager->pin($entry);
$entry = $this->manager->pin($entry, $this->getUserOrThrow());

$this->addFlash(
'success',
Expand Down
1 change: 1 addition & 0 deletions src/DTO/MagazineDto.php
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ class MagazineDto
public ?string $ip = null;
public ?string $apId = null;
public ?string $apProfileId = null;
public ?string $apFeaturedUrl = null;
private ?int $id = null;

public function getId(): ?int
Expand Down
2 changes: 2 additions & 0 deletions src/Entity/MagazineLog.php
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
'entry_restored' => MagazineLogEntryRestored::class,
'entry_comment_deleted' => MagazineLogEntryCommentDeleted::class,
'entry_comment_restored' => MagazineLogEntryCommentRestored::class,
'entry_pinned' => MagazineLogEntryPinned::class,
'entry_unpinned' => MagazineLogEntryUnpinned::class,
'post_deleted' => MagazineLogPostDeleted::class,
'post_restored' => MagazineLogPostRestored::class,
'post_comment_deleted' => MagazineLogPostCommentDeleted::class,
Expand Down
46 changes: 46 additions & 0 deletions src/Entity/MagazineLogEntryPinned.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

declare(strict_types=1);

namespace App\Entity;

use App\Entity\Contracts\ContentInterface;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\JoinColumn;
use Doctrine\ORM\Mapping\ManyToOne;

#[Entity]
class MagazineLogEntryPinned extends MagazineLog
{
#[ManyToOne(targetEntity: Entry::class)]
#[JoinColumn(nullable: true, onDelete: 'CASCADE')]
public ?Entry $entry = null;

#[ManyToOne(targetEntity: User::class)]
#[JoinColumn(nullable: true, onDelete: 'CASCADE')]
public ?User $actingUser;

public function __construct(Magazine $magazine, ?User $actingUser, Entry $unpinnedEntry)
{
parent::__construct($magazine, $unpinnedEntry->user);
$this->entry = $unpinnedEntry;
$this->actingUser = $actingUser;
}

public function getSubject(): ContentInterface|null
{
return $this->entry;
}

public function clearSubject(): MagazineLog
{
$this->entry = null;

return $this;
}

public function getType(): string
{
return 'log_entry_pinned';
}
}
46 changes: 46 additions & 0 deletions src/Entity/MagazineLogEntryUnpinned.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?php

declare(strict_types=1);

namespace App\Entity;

use App\Entity\Contracts\ContentInterface;
use Doctrine\ORM\Mapping\Entity;
use Doctrine\ORM\Mapping\JoinColumn;
use Doctrine\ORM\Mapping\ManyToOne;

#[Entity]
class MagazineLogEntryUnpinned extends MagazineLog
{
#[ManyToOne(targetEntity: Entry::class)]
#[JoinColumn(nullable: true, onDelete: 'CASCADE')]
public ?Entry $entry = null;

#[ManyToOne(targetEntity: User::class)]
#[JoinColumn(nullable: true, onDelete: 'CASCADE')]
public ?User $actingUser;

public function __construct(Magazine $magazine, ?User $actingUser, Entry $unpinnedEntry)
{
parent::__construct($magazine, $unpinnedEntry->user);
$this->entry = $unpinnedEntry;
$this->actingUser = $actingUser;
}

public function getSubject(): ContentInterface|null
{
return $this->entry;
}

public function clearSubject(): MagazineLog
{
$this->entry = null;

return $this;
}

public function getType(): string
{
return 'log_entry_unpinned';
}
}
3 changes: 3 additions & 0 deletions src/Entity/Traits/ActivityPubActorTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ trait ActivityPubActorTrait
#[Column(type: 'string', nullable: true)]
public ?string $apAttributedToUrl = null;

#[Column(type: 'string', nullable: true)]
public ?string $apFeaturedUrl = null;

#[Column(type: 'integer', nullable: true)]
public ?int $apFollowersCount = null;

Expand Down
3 changes: 2 additions & 1 deletion src/Event/Entry/EntryPinEvent.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,11 @@
namespace App\Event\Entry;

use App\Entity\Entry;
use App\Entity\User;

class EntryPinEvent
{
public function __construct(public Entry $entry)
public function __construct(public Entry $entry, public ?User $actor)
{
}
}
51 changes: 51 additions & 0 deletions src/EventSubscriber/Entry/EntryPinSubscriber.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

declare(strict_types=1);

namespace App\EventSubscriber\Entry;

use App\Event\Entry\EntryPinEvent;
use App\Factory\ActivityPub\AddRemoveFactory;
use App\Message\ActivityPub\Outbox\EntryPinMessage;
use App\Message\ActivityPub\Outbox\GenericAnnounceMessage;
use Psr\Log\LoggerInterface;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
use Symfony\Component\Messenger\MessageBusInterface;

class EntryPinSubscriber implements EventSubscriberInterface
{
public function __construct(
private readonly MessageBusInterface $bus,
private readonly LoggerInterface $logger,
private readonly AddRemoveFactory $addRemoveFactory,
) {
}

public static function getSubscribedEvents(): array
{
return [
EntryPinEvent::class => 'onEntryPin',
];
}

public function onEntryPin(EntryPinEvent $event): void
{
if ($event->actor && null === $event->actor->apId && $event->entry->magazine->userIsModerator($event->actor)) {
$this->logger->debug('entry {e} got {p} by {u}, dispatching new EntryPinMessage', ['e' => $event->entry->title, 'p' => $event->entry->sticky ? 'pinned' : 'unpinned', 'u' => $event->actor?->username ?? 'system']);
$this->bus->dispatch(new EntryPinMessage($event->entry->getId(), $event->entry->sticky, $event->actor?->getId()));
} elseif (null === $event->entry->magazine->apId && $event->actor && $event->entry->magazine->userIsModerator($event->actor)) {
if (null !== $event->actor->apId) {
if ($event->entry->sticky) {
$activity = $this->addRemoveFactory->buildAddPinnedPost($event->actor, $event->entry);
} else {
$activity = $this->addRemoveFactory->buildRemovePinnedPost($event->actor, $event->entry);
}
$this->logger->debug('dispatching announce for add pin post {e} by {u} in {m}', ['e' => $event->entry->title, 'u' => $event->actor->apId, 'm' => $event->entry->magazine->name]);
$this->bus->dispatch(new GenericAnnounceMessage($event->entry->magazine->getId(), $activity));
} else {
$this->logger->debug('entry {e} got {p} by {u}, dispatching new EntryPinMessage', ['e' => $event->entry->title, 'p' => $event->entry->sticky ? 'pinned' : 'unpinned', 'u' => $event->actor?->username ?? 'system']);
$this->bus->dispatch(new EntryPinMessage($event->entry->getId(), $event->entry->sticky, $event->actor?->getId()));
}
}
}
}
Loading
Loading