[COMPONENT][Subscription] Start component

This commit is contained in:
Diogo Peralta Cordeiro 2022-01-02 20:00:17 +00:00
parent 5fa8056899
commit 6cfb69d64b
Signed by: diogo
GPG Key ID: 18D2D35001FBFAB0
7 changed files with 77 additions and 46 deletions

View File

@ -39,6 +39,7 @@ use Component\Collection\Util\ActorControllerTrait;
use Component\Collection\Util\Controller\FeedController; use Component\Collection\Util\Controller\FeedController;
use Component\Group\Entity\GroupMember; use Component\Group\Entity\GroupMember;
use Component\Group\Entity\LocalGroup; use Component\Group\Entity\LocalGroup;
use Component\Subscription\Entity\Subscription;
use Symfony\Component\Form\Extension\Core\Type\SubmitType; use Symfony\Component\Form\Extension\Core\Type\SubmitType;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
@ -91,7 +92,7 @@ class Group extends FeedController
'group_id' => $group->getId(), 'group_id' => $group->getId(),
'nickname' => $nickname, 'nickname' => $nickname,
])); ]));
DB::persist(E\Subscription::create([ DB::persist(Subscription::create([
'subscriber' => $group->getId(), 'subscriber' => $group->getId(),
'subscribed' => $group->getId(), 'subscribed' => $group->getId(),
])); ]));
@ -115,7 +116,7 @@ class Group extends FeedController
} else { } else {
if (!\is_null($actor) if (!\is_null($actor)
&& \is_null(Cache::get( && \is_null(Cache::get(
E\Subscription::cacheKeys($actor, $group)['subscribed'], Subscription::cacheKeys($actor, $group)['subscribed'],
fn () => DB::findOneBy('subscription', [ fn () => DB::findOneBy('subscription', [
'subscriber' => $actor->getId(), 'subscriber' => $actor->getId(),
'subscribed' => $group->getId(), 'subscribed' => $group->getId(),
@ -125,14 +126,14 @@ class Group extends FeedController
$subscribe_form = Form::create([['subscribe', SubmitType::class, ['label' => _m('Subscribe to this group')]]]); $subscribe_form = Form::create([['subscribe', SubmitType::class, ['label' => _m('Subscribe to this group')]]]);
$subscribe_form->handleRequest($request); $subscribe_form->handleRequest($request);
if ($subscribe_form->isSubmitted() && $subscribe_form->isValid()) { if ($subscribe_form->isSubmitted() && $subscribe_form->isValid()) {
DB::persist(E\Subscription::create([ DB::persist(Subscription::create([
'subscriber' => $actor->getId(), 'subscriber' => $actor->getId(),
'subscribed' => $group->getId(), 'subscribed' => $group->getId(),
])); ]));
DB::flush(); DB::flush();
Cache::delete(E\Actor::cacheKeys($group->getId())['subscriber']); Cache::delete(E\Actor::cacheKeys($group->getId())['subscriber']);
Cache::delete(E\Actor::cacheKeys($actor->getId())['subscribed']); Cache::delete(E\Actor::cacheKeys($actor->getId())['subscribed']);
Cache::delete(E\Subscription::cacheKeys($actor, $group)['subscribed']); Cache::delete(Subscription::cacheKeys($actor, $group)['subscribed']);
} }
} }
} }

View File

@ -21,23 +21,25 @@ declare(strict_types = 1);
// }}} // }}}
namespace App\Controller; namespace Component\Subscription\Controller;
use App\Core\Controller\ActorController; use function App\Core\I18n\_m;
use Component\Collection\Util\ActorControllerTrait;
use Component\Collection\Util\Controller\CircleController;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
/** /**
* Collection of an actor's subscribers * Collection of an actor's subscribers
*/ */
class Subscribers extends ActorController class Subscribers extends CircleController
{ {
use ActorControllerTrait;
public function subscribersByActorId(Request $request, int $id) public function subscribersByActorId(Request $request, int $id)
{ {
return $this->handleActorById( return $this->handleActorById(
$id, $id,
fn ($actor) => [ fn ($actor) => [
'_template' => 'subscribers/view.html.twig', 'actor' => $actor,
'actor' => $actor,
], ],
); );
} }
@ -47,8 +49,12 @@ class Subscribers extends ActorController
return $this->handleActorByNickname( return $this->handleActorByNickname(
$nickname, $nickname,
fn ($actor) => [ fn ($actor) => [
'_template' => 'subscribers/view.html.twig', '_template' => 'collection/actors.html.twig',
'actor' => $actor, 'title' => _m('Subscribers'),
'empty_message' => _m('No subscribers'),
'sort_options' => [],
'page' => $this->int('page') ?? 1,
'actors' => $actor->getSubscribers(),
], ],
); );
} }

View File

@ -21,23 +21,25 @@ declare(strict_types = 1);
// }}} // }}}
namespace App\Controller; namespace Component\Subscription\Controller;
use App\Core\Controller\ActorController; use function App\Core\I18n\_m;
use Component\Collection\Util\ActorControllerTrait;
use Component\Collection\Util\Controller\CircleController;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
/** /**
* Collection of an actor's subscriptions * Collection of an actor's subscriptions
*/ */
class Subscriptions extends ActorController class Subscriptions extends CircleController
{ {
use ActorControllerTrait;
public function subscriptionsByActorId(Request $request, int $id) public function subscriptionsByActorId(Request $request, int $id)
{ {
return $this->handleActorById( return $this->handleActorById(
$id, $id,
fn ($actor) => [ fn ($actor) => [
'_template' => 'subscriptions/view.html.twig', 'actor' => $actor,
'actor' => $actor,
], ],
); );
} }
@ -47,8 +49,12 @@ class Subscriptions extends ActorController
return $this->handleActorByNickname( return $this->handleActorByNickname(
$nickname, $nickname,
fn ($actor) => [ fn ($actor) => [
'_template' => 'subscriptions/view.html.twig', '_template' => 'collection/actors.html.twig',
'actor' => $actor, 'title' => _m('Subscribers'),
'empty_message' => _m('No subscribers'),
'sort_options' => [],
'page' => $this->int('page') ?? 1,
'actors' => $actor->getSubscribers(),
], ],
); );
} }

View File

@ -9,10 +9,10 @@ use App\Core\VisibilityScope;
use App\Entity\Actor; use App\Entity\Actor;
use App\Entity\LocalUser; use App\Entity\LocalUser;
use App\Entity\Note; use App\Entity\Note;
use App\Entity\Subscription;
use Component\Group\Entity\GroupInbox; use Component\Group\Entity\GroupInbox;
use Component\Group\Entity\GroupMember; use Component\Group\Entity\GroupMember;
use Component\Group\Entity\LocalGroup; use Component\Group\Entity\LocalGroup;
use Component\Subscription\Entity\Subscription;
use Doctrine\Bundle\FixturesBundle\Fixture; use Doctrine\Bundle\FixturesBundle\Fixture;
use Doctrine\Persistence\ObjectManager; use Doctrine\Persistence\ObjectManager;

View File

@ -37,6 +37,7 @@ use App\Util\Nickname;
use Component\Avatar\Avatar; use Component\Avatar\Avatar;
use Component\Language\Entity\ActorLanguage; use Component\Language\Entity\ActorLanguage;
use Component\Language\Entity\Language; use Component\Language\Entity\Language;
use Component\Subscription\Entity\Subscription;
use DateTimeInterface; use DateTimeInterface;
use Functional as F; use Functional as F;
@ -242,26 +243,26 @@ class Actor extends Entity
// @codeCoverageIgnoreEnd // @codeCoverageIgnoreEnd
// }}} Autocode // }}} Autocode
public const PERSON = 1; public const PERSON = 1;
public const GROUP = 2; public const GROUP = 2;
public const ORGANIZATION = 3; public const ORGANIZATION = 3;
public const BUSINESS = 4; public const BUSINESS = 4;
public const BOT = 5; public const BOT = 5;
public static function cacheKeys(int|self $actor_id, mixed $other = null): array public static function cacheKeys(int|self $actor_id, mixed $other = null): array
{ {
$actor_id = \is_int($actor_id) ? $actor_id : $actor_id->getId(); $actor_id = \is_int($actor_id) ? $actor_id : $actor_id->getId();
return [ return [
'id' => "actor-id-{$actor_id}", 'id' => "actor-id-{$actor_id}",
'nickname' => "actor-nickname-id-{$actor_id}", 'nickname' => "actor-nickname-id-{$actor_id}",
'fullname' => "actor-fullname-id-{$actor_id}", 'fullname' => "actor-fullname-id-{$actor_id}",
'tags' => \is_null($other) ? "actor-tags-{$actor_id}" : "actor-tags-{$actor_id}-by-{$other}", // $other is $context_id 'tags' => \is_null($other) ? "actor-tags-{$actor_id}" : "actor-tags-{$actor_id}-by-{$other}", // $other is $context_id
'circles' => "actor-circles-{$actor_id}", 'circles' => "actor-circles-{$actor_id}",
'subscriber' => "subscriber-{$actor_id}", 'subscriber' => "subscriber-{$actor_id}",
'subscribed' => "subscribed-{$actor_id}", 'subscribed' => "subscribed-{$actor_id}",
'relative-nickname' => "actor-{$actor_id}-relative-nickname-{$other}", // $other is $nickname 'relative-nickname' => "actor-{$actor_id}-relative-nickname-{$other}", // $other is $nickname
'can-admin' => "actor-{$actor_id}-can-admin-{$other}", // $other is an actor id 'can-admin' => "actor-{$actor_id}-can-admin-{$other}", // $other is an actor id
]; ];
} }
@ -286,17 +287,17 @@ class Actor extends Entity
public static function getById(int $id): ?self public static function getById(int $id): ?self
{ {
return Cache::get(self::cacheKeys($id)['id'], fn () => DB::find('actor', ['id' => $id])); return Cache::get(self::cacheKeys($id)['id'], fn() => DB::find('actor', ['id' => $id]));
} }
public static function getNicknameById(int $id): string public static function getNicknameById(int $id): string
{ {
return Cache::get(self::cacheKeys($id)['nickname'], fn () => self::getById($id)->getNickname()); return Cache::get(self::cacheKeys($id)['nickname'], fn() => self::getById($id)->getNickname());
} }
public static function getFullnameById(int $id): ?string public static function getFullnameById(int $id): ?string
{ {
return Cache::get(self::cacheKeys($id)['fullname'], fn () => self::getById($id)->getFullname()); return Cache::get(self::cacheKeys($id)['fullname'], fn() => self::getById($id)->getFullname());
} }
/** /**
@ -324,8 +325,8 @@ class Actor extends Entity
* - If null = All tags attributed to self by other actors (excludes self tags) * - If null = All tags attributed to self by other actors (excludes self tags)
* - If self = Same as getSelfTags * - If self = Same as getSelfTags
* - otherwise = Tags that $context attributed to $this * - otherwise = Tags that $context attributed to $this
* @param null|int $offset Offset from latest * @param null|int $offset Offset from latest
* @param null|int $limit Max number to get * @param null|int $limit Max number to get
* *
* @return ActorTag[] resulting lists * @return ActorTag[] resulting lists
*/ */
@ -334,7 +335,7 @@ class Actor extends Entity
if (\is_null($context)) { if (\is_null($context)) {
return Cache::getList( return Cache::getList(
self::cacheKeys($this->getId())['tags'], self::cacheKeys($this->getId())['tags'],
fn () => DB::dql( fn() => DB::dql(
<<< 'EOQ' <<< 'EOQ'
SELECT tag SELECT tag
FROM actor_tag tag FROM actor_tag tag
@ -349,7 +350,7 @@ class Actor extends Entity
$context_id = \is_int($context) ? $context : $context->getId(); $context_id = \is_int($context) ? $context : $context->getId();
return Cache::getList( return Cache::getList(
self::cacheKeys($this->getId(), $context_id)['tags'], self::cacheKeys($this->getId(), $context_id)['tags'],
fn () => DB::dql( fn() => DB::dql(
<<< 'EOQ' <<< 'EOQ'
SELECT tag SELECT tag
FROM actor_tag tag FROM actor_tag tag
@ -367,7 +368,7 @@ class Actor extends Entity
{ {
return Cache::getList( return Cache::getList(
self::cacheKeys($this->getId())['circles'], self::cacheKeys($this->getId())['circles'],
fn () => DB::findBy('actor_circle', ['tagger' => $this->getId()]), fn() => DB::findBy('actor_circle', ['tagger' => $this->getId()]),
); );
} }
@ -375,10 +376,7 @@ class Actor extends Entity
{ {
return Cache::get( return Cache::get(
self::cacheKeys($this->getId())[$which], self::cacheKeys($this->getId())[$which],
fn () => DB::dql( fn() => DB::count(Subscription::class, [$column => $this->getId()]) - ($this->getIsLocal() ? 1 : 0)
"select count(s) from subscription s where s.{$column} = :{$column}", // Not injecting the parameter value
[$column => $this->getId()],
)[0][1] - ($this->getIsLocal() ? 1 : 0), // Remove self subscription if local
); );
} }
@ -387,11 +385,31 @@ class Actor extends Entity
return $this->getSubCount(which: 'subscriber', column: 'subscribed_id'); return $this->getSubCount(which: 'subscriber', column: 'subscribed_id');
} }
public function getSubscribedCount() public function getSubscribedCount(): int
{ {
return $this->getSubCount(which: 'subscribed', column: 'subscriber_id'); return $this->getSubCount(which: 'subscribed', column: 'subscriber_id');
} }
public function getSubscriptions(): array
{
return DB::dql(<<<EOF
SELECT a FROM actor AS a
INNER JOIN subscription AS s
WITH a.id = s.subscribed_id
WHERE s.subscriber_id = :self AND a.id != :self
EOF, ['self' => $this->getId()]);
}
public function getSubscribers(): array
{
return DB::dql(<<<EOF
SELECT a FROM actor AS a
INNER JOIN subscription AS s
WITH a.id = s.subscriber_id
WHERE s.subscribed_id = :self AND a.id != :self
EOF, ['self' => $this->getId()]);
}
/** /**
* Resolve an ambiguous nickname reference, checking in following order: * Resolve an ambiguous nickname reference, checking in following order:
* - Actors that $sender subscribes to * - Actors that $sender subscribes to

View File

@ -34,9 +34,9 @@ declare(strict_types = 1);
namespace App\Routes; namespace App\Routes;
use App\Controller as C;
use App\Core\Router\RouteLoader; use App\Core\Router\RouteLoader;
use App\Util\Nickname; use App\Util\Nickname;
use Component\Subscription\Controller as C;
abstract class Subscribers abstract class Subscribers
{ {

View File

@ -34,9 +34,9 @@ declare(strict_types = 1);
namespace App\Routes; namespace App\Routes;
use App\Controller as C;
use App\Core\Router\RouteLoader; use App\Core\Router\RouteLoader;
use App\Util\Nickname; use App\Util\Nickname;
use Component\Subscription\Controller as C;
abstract class Subscriptions abstract class Subscriptions
{ {