diff --git a/lib/Command/CheckInstall.php b/lib/Command/CheckInstall.php index 0b41a8cb..7fecbfc0 100644 --- a/lib/Command/CheckInstall.php +++ b/lib/Command/CheckInstall.php @@ -31,13 +31,13 @@ declare(strict_types=1); namespace OCA\Social\Command; +use daita\MySmallPhpTools\Traits\TArrayTools; use Exception; use OC\Core\Command\Base; use OCA\Social\Service\CheckService; use OCA\Social\Service\MiscService; use OCA\Social\Service\PushService; use OCP\IUserManager; -use OCP\Push\Exceptions\PushInstallException; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\OutputInterface; @@ -46,6 +46,9 @@ use Symfony\Component\Console\Output\OutputInterface; class CheckInstall extends Base { + use TArrayTools; + + /** @var IUserManager */ private $userManager; @@ -86,7 +89,8 @@ class CheckInstall extends Base { parent::configure(); $this->setName('social:check:install') ->addOption( - 'push', '', InputOption::VALUE_REQUIRED, 'a local account used to test integration to Nextcloud Push', + 'push', '', InputOption::VALUE_REQUIRED, + 'a local account used to test integration to Nextcloud Push', '' ) ->setDescription('Check the integrity of the installation'); @@ -100,9 +104,14 @@ class CheckInstall extends Base { * @throws Exception */ protected function execute(InputInterface $input, OutputInterface $output) { - $this->checkService->checkInstallationStatus(); + $result = $this->checkService->checkInstallationStatus(); - $this->checkPushApp($input, $output); + if ($this->checkPushApp($input, $output)) { + return; + } + + $output->writeln('- ' . $this->getInt('invalidFollowers', $result, 0) . ' invalid followers removed'); + $output->writeln('- ' . $this->getInt('invalidNotes', $result, 0) . ' invalid notes removed'); } @@ -110,20 +119,25 @@ class CheckInstall extends Base { * @param InputInterface $input * @param OutputInterface $output * + * @return bool * @throws Exception */ - private function checkPushApp(InputInterface $input, OutputInterface $output) { + private function checkPushApp(InputInterface $input, OutputInterface $output): bool { $userId = $input->getOption('push'); - if ($userId !== '') { - $user = $this->userManager->get($userId); - if ($user === null) { - throw new Exception('unknown user'); - } - - $wrapper = $this->pushService->testOnAccount($userId); - - $output->writeln(json_encode($wrapper, JSON_PRETTY_PRINT)); + if ($userId === '') { + return false; } + + $user = $this->userManager->get($userId); + if ($user === null) { + throw new Exception('unknown user'); + } + + $wrapper = $this->pushService->testOnAccount($userId); + + $output->writeln(json_encode($wrapper, JSON_PRETTY_PRINT)); + + return true; } } diff --git a/lib/Controller/LocalController.php b/lib/Controller/LocalController.php index d3adb361..bd0bae2a 100644 --- a/lib/Controller/LocalController.php +++ b/lib/Controller/LocalController.php @@ -325,11 +325,11 @@ class LocalController extends Controller { public function postUnlike(string $postId): DataResponse { try { $this->initViewer(true); - $announce = $this->likeService->delete($this->viewer, $postId, $token); + $like = $this->likeService->delete($this->viewer, $postId, $token); return $this->success( [ - 'like' => $announce, + 'like' => $like, 'token' => $token ] ); @@ -728,7 +728,7 @@ class LocalController extends Controller { public function globalActorAvatar(string $id): Response { try { $actor = $this->cacheActorService->getFromId($id); - if ($actor->gotIcon()) { + if ($actor->hasIcon()) { $avatar = $actor->getIcon(); $mime = ''; $document = $this->documentService->getFromCache($avatar->getId(), $mime); diff --git a/lib/Controller/NavigationController.php b/lib/Controller/NavigationController.php index 8c27aa59..0160a72b 100644 --- a/lib/Controller/NavigationController.php +++ b/lib/Controller/NavigationController.php @@ -149,7 +149,7 @@ class NavigationController extends Controller { try { $data['serverData']['cloudAddress'] = $this->configService->getCloudUrl(); } catch (SocialAppConfigException $e) { - $this->checkService->checkInstallationStatus(); + $this->checkService->checkInstallationStatus(true); $cloudAddress = $this->setupCloudAddress(); if ($cloudAddress !== '') { $data['serverData']['cloudAddress'] = $cloudAddress; diff --git a/lib/Db/CacheActorsRequest.php b/lib/Db/CacheActorsRequest.php index 3867d7bd..8f5bda80 100644 --- a/lib/Db/CacheActorsRequest.php +++ b/lib/Db/CacheActorsRequest.php @@ -102,7 +102,7 @@ class CacheActorsRequest extends CacheActorsRequestBuilder { } catch (Exception $e) { } - if ($actor->gotIcon()) { + if ($actor->hasIcon()) { $iconId = $actor->getIcon() ->getId(); } else { @@ -160,7 +160,7 @@ class CacheActorsRequest extends CacheActorsRequestBuilder { } catch (Exception $e) { } - if ($actor->gotIcon()) { + if ($actor->hasIcon()) { $iconId = $actor->getIcon() ->getId(); } else { @@ -301,13 +301,12 @@ class CacheActorsRequest extends CacheActorsRequestBuilder { * * @param string $id */ - public function deleteFromId(string $id) { + public function deleteCacheById(string $id) { $qb = $this->getCacheActorsDeleteSql(); $this->limitToIdString($qb, $id); $qb->execute(); } - } diff --git a/lib/Db/FollowsRequest.php b/lib/Db/FollowsRequest.php index 77ab6894..dea89655 100644 --- a/lib/Db/FollowsRequest.php +++ b/lib/Db/FollowsRequest.php @@ -92,6 +92,23 @@ class FollowsRequest extends FollowsRequestBuilder { } + /** + * @return Follow[] + */ + public function getAll(): array { + $qb = $this->getFollowsSelectSql(); + + $follows = []; + $cursor = $qb->execute(); + while ($data = $cursor->fetch()) { + $follows[] = $this->parseFollowsSelectSql($data); + } + $cursor->closeCursor(); + + return $follows; + } + + /** * @param string $actorId * @param string $remoteActorId @@ -267,7 +284,6 @@ class FollowsRequest extends FollowsRequestBuilder { $qb->execute(); } - /** * @param Follow $follow */ @@ -279,6 +295,30 @@ class FollowsRequest extends FollowsRequestBuilder { $qb->execute(); } + /** + * @param string $actorId + */ + public function deleteRelatedId(string $actorId) { + $qb = $this->getFollowsDeleteSql(); + $this->limitToActorId($qb, $actorId); + + $qb->execute(); + + $qb = $this->getFollowsDeleteSql(); + $this->limitToObjectId($qb, $actorId); + + $qb->execute(); + } + + /** + * @param string $id + */ + public function deleteById(string $id) { + $qb = $this->getFollowsDeleteSql(); + $this->limitToIdString($qb, $id); + + $qb->execute(); + } } diff --git a/lib/Db/StreamRequest.php b/lib/Db/StreamRequest.php index db2dee29..71da370b 100644 --- a/lib/Db/StreamRequest.php +++ b/lib/Db/StreamRequest.php @@ -195,6 +195,32 @@ class StreamRequest extends StreamRequestBuilder { } + /** + * @param string $type + * + * @return Stream[] + */ + public function getAll(string $type = ''): array { + $qb = $this->getStreamSelectSql(); + + if ($type !== '') { + $this->limitToType($qb, $type); + } + + $streams = []; + $cursor = $qb->execute(); + while ($data = $cursor->fetch()) { + try { + $streams[] = $this->parseStreamSelectSql($data); + } catch (Exception $e) { + } + } + $cursor->closeCursor(); + + return $streams; + } + + /** * @param string $id * @param bool $asViewer @@ -614,10 +640,10 @@ class StreamRequest extends StreamRequestBuilder { * @param string $id * @param string $type */ - public function deleteStreamById(string $id, string $type = '') { + public function deleteById(string $id, string $type = '') { $qb = $this->getStreamDeleteSql(); - $this->limitToIdString($qb, $id); + if ($type !== '') { $this->limitToType($qb, $type); } diff --git a/lib/Interfaces/Activity/DeleteInterface.php b/lib/Interfaces/Activity/DeleteInterface.php index f087cf97..e0952733 100644 --- a/lib/Interfaces/Activity/DeleteInterface.php +++ b/lib/Interfaces/Activity/DeleteInterface.php @@ -63,26 +63,19 @@ class DeleteInterface implements IActivityPubInterface { */ public function processIncomingRequest(ACore $item) { $item->checkOrigin($item->getId()); + $item->checkOrigin($item->getObjectId()); if (!$item->hasObject()) { + $types = ['Note', 'Person']; + foreach ($types as $type) { + try { + $interface = AP::$activityPub->getInterfaceFromType($type); + $object = $interface->getItemById($item->getObjectId()); + $interface->delete($object); - if ($item->getObjectId() !== '') { - $item->checkOrigin($item->getObjectId()); - - // TODO: migrate to activity() !! - $types = ['Note', 'Person']; - foreach ($types as $type) { - try { - $item->checkOrigin($item->getObjectId()); - $interface = AP::$activityPub->getInterfaceFromType($type); - $object = $interface->getItemById($item->getObjectId()); - $interface->delete($object); - - return; - } catch (InvalidOriginException $e) { - } catch (ItemNotFoundException $e) { - } catch (ItemUnknownException $e) { - } + return; + } catch (ItemNotFoundException $e) { + } catch (ItemUnknownException $e) { } } @@ -91,11 +84,8 @@ class DeleteInterface implements IActivityPubInterface { $object = $item->getObject(); try { - $item->checkOrigin($object->getId()); - // FIXME: needed ? better use activity() -// $interface = AP::$activityPub->getInterfaceForItem($object); -// $interface->delete($object); - } catch (InvalidOriginException $e) { + $interface = AP::$activityPub->getInterfaceForItem($object); + $interface->activity($item, $object); } catch (ItemUnknownException $e) { } } diff --git a/lib/Interfaces/Actor/PersonInterface.php b/lib/Interfaces/Actor/PersonInterface.php index ef04c1f3..f37aa450 100644 --- a/lib/Interfaces/Actor/PersonInterface.php +++ b/lib/Interfaces/Actor/PersonInterface.php @@ -33,6 +33,7 @@ namespace OCA\Social\Interfaces\Actor; use daita\MySmallPhpTools\Traits\TArrayTools; use OCA\Social\Db\CacheActorsRequest; +use OCA\Social\Db\FollowsRequest; use OCA\Social\Db\StreamRequest; use OCA\Social\Exceptions\CacheActorDoesNotExistException; use OCA\Social\Exceptions\InvalidOriginException; @@ -63,6 +64,9 @@ class PersonInterface implements IActivityPubInterface { /** @var StreamRequest */ private $streamRequest; + /** @var FollowsRequest */ + private $followsRequest; + /** @var ActorService */ private $actorService; @@ -78,16 +82,19 @@ class PersonInterface implements IActivityPubInterface { * * @param CacheActorsRequest $cacheActorsRequest * @param StreamRequest $streamRequest + * @param FollowsRequest $followsRequest * @param ActorService $actorService * @param ConfigService $configService * @param MiscService $miscService */ public function __construct( CacheActorsRequest $cacheActorsRequest, StreamRequest $streamRequest, - ActorService $actorService, ConfigService $configService, MiscService $miscService + FollowsRequest $followsRequest, ActorService $actorService, ConfigService $configService, + MiscService $miscService ) { $this->cacheActorsRequest = $cacheActorsRequest; $this->streamRequest = $streamRequest; + $this->followsRequest = $followsRequest; $this->actorService = $actorService; $this->configService = $configService; $this->miscService = $miscService; @@ -178,8 +185,9 @@ class PersonInterface implements IActivityPubInterface { */ public function delete(ACore $item) { /** @var Person $item */ - $this->cacheActorsRequest->deleteFromId($item->getId()); + $this->cacheActorsRequest->deleteCacheById($item->getId()); $this->streamRequest->deleteByAuthor($item->getId()); + $this->followsRequest->deleteRelatedId($item->getId()); } diff --git a/lib/Interfaces/Internal/SocialAppNotificationInterface.php b/lib/Interfaces/Internal/SocialAppNotificationInterface.php index e15e1d1b..2ae45b9b 100644 --- a/lib/Interfaces/Internal/SocialAppNotificationInterface.php +++ b/lib/Interfaces/Internal/SocialAppNotificationInterface.php @@ -157,7 +157,7 @@ class SocialAppNotificationInterface implements IActivityPubInterface { */ public function delete(ACore $item) { /** @var Stream $item */ - $this->streamRequest->deleteStreamById($item->getId(), SocialAppNotification::TYPE); + $this->streamRequest->deleteById($item->getId(), SocialAppNotification::TYPE); } diff --git a/lib/Interfaces/Object/AnnounceInterface.php b/lib/Interfaces/Object/AnnounceInterface.php index 1dc62a67..182232c2 100644 --- a/lib/Interfaces/Object/AnnounceInterface.php +++ b/lib/Interfaces/Object/AnnounceInterface.php @@ -277,7 +277,7 @@ class AnnounceInterface implements IActivityPubInterface { $knownItem->removeCc($actor->getFollowers()); if (empty($knownItem->getCcArray())) { - $this->streamRequest->deleteStreamById($knownItem->getId(), Announce::TYPE); + $this->streamRequest->deleteById($knownItem->getId(), Announce::TYPE); } else { $this->streamRequest->update($knownItem); } diff --git a/lib/Interfaces/Object/NoteInterface.php b/lib/Interfaces/Object/NoteInterface.php index 0da2fca8..4210dd0a 100644 --- a/lib/Interfaces/Object/NoteInterface.php +++ b/lib/Interfaces/Object/NoteInterface.php @@ -38,6 +38,7 @@ use OCA\Social\Exceptions\StreamNotFoundException; use OCA\Social\Interfaces\IActivityPubInterface; use OCA\Social\Model\ActivityPub\ACore; use OCA\Social\Model\ActivityPub\Activity\Create; +use OCA\Social\Model\ActivityPub\Activity\Delete; use OCA\Social\Model\ActivityPub\Object\Note; use OCA\Social\Service\ConfigService; use OCA\Social\Service\CurlService; @@ -140,6 +141,11 @@ class NoteInterface implements IActivityPubInterface { $item->setActivityId($activity->getId()); $this->save($item); } + + if ($activity->getType() === Delete::TYPE) { + $activity->checkOrigin($item->getId()); + $this->delete($item); + } } @@ -167,14 +173,10 @@ class NoteInterface implements IActivityPubInterface { /** * @param ACore $item - * - * @throws InvalidOriginException */ public function delete(ACore $item) { - $item->checkOrigin(($item->getId())); - /** @var Note $item */ - $this->streamRequest->deleteStreamById($item->getId(), Note::TYPE); + $this->streamRequest->deleteById($item->getId(), Note::TYPE); } diff --git a/lib/Migration/CheckInstallation.php b/lib/Migration/CheckInstallation.php index a3b58915..094f6896 100644 --- a/lib/Migration/CheckInstallation.php +++ b/lib/Migration/CheckInstallation.php @@ -73,7 +73,7 @@ class CheckInstallation implements IRepairStep { * @param IOutput $output */ public function run(IOutput $output) { - $this->checkService->checkInstallationStatus(); + $this->checkService->checkInstallationStatus(true); } diff --git a/lib/Model/ActivityPub/ACore.php b/lib/Model/ActivityPub/ACore.php index 471968f7..d139595b 100644 --- a/lib/Model/ActivityPub/ACore.php +++ b/lib/Model/ActivityPub/ACore.php @@ -171,6 +171,18 @@ class ACore extends Item implements JsonSerializable { return $this; } + /** + * @return string + */ + public function getObjectId(): string { + if ($this->hasObject()) { + return $this->getObject() + ->getId(); + } + + return parent::getObjectId(); + } + /** * @param bool $filter - will remove general url like Public @@ -191,7 +203,7 @@ class ACore extends Item implements JsonSerializable { /** * @return bool */ - public function gotIcon(): bool { + public function hasIcon(): bool { if ($this->icon === null) { return false; } @@ -306,7 +318,7 @@ class ACore extends Item implements JsonSerializable { $origin = $this->getRoot() ->getOrigin(); - if ($origin === $host) { + if ($id !== '' && $origin === $host && $host !== '') { return; } @@ -684,7 +696,7 @@ class ACore extends Item implements JsonSerializable { } // TODO - moving the $this->icon to Model/Person ? - if ($this->gotIcon()) { + if ($this->hasIcon()) { $this->addEntryItem('icon', $this->getIcon()); } diff --git a/lib/Service/ActorService.php b/lib/Service/ActorService.php index 78b6620c..429ebf88 100644 --- a/lib/Service/ActorService.php +++ b/lib/Service/ActorService.php @@ -131,7 +131,7 @@ class ActorService { * @param Person $actor */ private function cacheDocumentIfNeeded(Person $actor) { - if ($actor->gotIcon()) { + if ($actor->hasIcon()) { $icon = $actor->getIcon(); try { $cache = $this->cacheDocumentsRequest->getByUrl($icon->getUrl()); diff --git a/lib/Service/CheckService.php b/lib/Service/CheckService.php index 3e65a26f..500e13c2 100644 --- a/lib/Service/CheckService.php +++ b/lib/Service/CheckService.php @@ -28,8 +28,12 @@ use daita\MySmallPhpTools\Traits\TArrayTools; use daita\MySmallPhpTools\Traits\TStringTools; use Exception; use GuzzleHttp\Exception\ClientException; +use OCA\Social\Db\CacheActorsRequest; use OCA\Social\Db\FollowsRequest; +use OCA\Social\Db\StreamRequest; +use OCA\Social\Exceptions\CacheActorDoesNotExistException; use OCA\Social\Model\ActivityPub\Object\Follow; +use OCA\Social\Model\ActivityPub\Object\Note; use OCP\AppFramework\Http; use OCP\Http\Client\IClientService; use OCP\ICache; @@ -68,11 +72,20 @@ class CheckService { /** @var IURLGenerator */ private $urlGenerator; + /** @var FollowsRequest */ + private $followRequest; + + /** @var CacheActorsRequest */ + private $cacheActorsRequest; + + /** @var StreamRequest */ + private $streamRequest; + /** @var ConfigService */ private $configService; - /** @var FollowsRequest */ - private $followRequest; + /** @var MiscService */ + private $miscService; /** @@ -84,11 +97,17 @@ class CheckService { * @param IRequest $request * @param IURLGenerator $urlGenerator * @param FollowsRequest $followRequest + * @param CacheActorsRequest $cacheActorsRequest + * @param StreamRequest $streamRequest * @param ConfigService $configService + * @param MiscService $miscService */ public function __construct( ICache $cache, IConfig $config, IClientService $clientService, IRequest $request, - IURLGenerator $urlGenerator, FollowsRequest $followRequest, ConfigService $configService + IURLGenerator $urlGenerator, FollowsRequest $followRequest, + CacheActorsRequest $cacheActorsRequest, StreamRequest $streamRequest, + ConfigService $configService, + MiscService $miscService ) { $this->cache = $cache; $this->config = $config; @@ -96,7 +115,10 @@ class CheckService { $this->request = $request; $this->urlGenerator = $urlGenerator; $this->followRequest = $followRequest; + $this->cacheActorsRequest = $cacheActorsRequest; + $this->streamRequest = $streamRequest; $this->configService = $configService; + $this->miscService = $miscService; } @@ -151,12 +173,24 @@ class CheckService { /** + * @param bool $light * + * @return array */ - public function checkInstallationStatus() { + public function checkInstallationStatus(bool $light = false): array { $this->configService->setCoreValue('public_webfinger', 'social/lib/webfinger.php'); $this->configService->setCoreValue('public_host-meta', 'social/lib/hostmeta.php'); + + if (!$light) { + $result = [ + 'invalidFollows' => $this->removeInvalidFollows(), + 'invalidNotes' => $this->removeInvalidNotes() + ]; + } + $this->checkStatusTableFollows(); + + return $result; } @@ -179,6 +213,50 @@ class CheckService { } + /** + * @return int + */ + public function removeInvalidFollows(): int { + $count = 0; + $follows = $this->followRequest->getAll(); + foreach ($follows as $follow) { + try { + $this->cacheActorsRequest->getFromId($follow->getActorId()); + $this->cacheActorsRequest->getFromId($follow->getObjectId()); + } catch (CacheActorDoesNotExistException $e) { + $this->followRequest->deleteById($follow->getId()); + $count++; + } + } + + $this->miscService->log('removeInvalidFollows removed ' . $count . ' entries', 1); + + return $count; + } + + + /** + * @return int + */ + public function removeInvalidNotes(): int { + $count = 0; + $streams = $this->streamRequest->getAll(Note::TYPE); + foreach ($streams as $stream) { + try { + // Check if it's enough for Note, Announce, ... + $this->cacheActorsRequest->getFromId($stream->getAttributedTo()); + } catch (CacheActorDoesNotExistException $e) { + $this->streamRequest->deleteById($stream->getId(), Note::TYPE); + $count++; + } + } + + $this->miscService->log('removeInvalidNotes removed ' . $count . ' entries', 1); + + return $count; + } + + /** * @param string $base * diff --git a/lib/Service/StreamService.php b/lib/Service/StreamService.php index 0e09b7b4..75bb7957 100644 --- a/lib/Service/StreamService.php +++ b/lib/Service/StreamService.php @@ -363,7 +363,7 @@ class StreamService { $item->setActorId($item->getAttributedTo()); $this->activityService->deleteActivity($item); - $this->streamRequest->deleteStreamById($item->getId(), $type); + $this->streamRequest->deleteById($item->getId(), $type); }