diff --git a/components/Collection/Collection.php b/components/Collection/Collection.php index 8c1cec9188..b44ddf31e7 100644 --- a/components/Collection/Collection.php +++ b/components/Collection/Collection.php @@ -32,6 +32,7 @@ class Collection extends Component } $note_qb = DB::createQueryBuilder(); $actor_qb = DB::createQueryBuilder(); + // TODO consider selecting note related stuff, to avoid separate queries (though they're cached, so maybe it's okay) $note_qb->select('note')->from('App\Entity\Note', 'note')->orderBy('note.created', 'DESC')->addOrderBy('note.id', 'DESC'); $actor_qb->select('actor')->from('App\Entity\Actor', 'actor')->orderBy('actor.created', 'DESC')->addOrderBy('actor.id', 'DESC'); Event::handle('CollectionQueryAddJoins', [&$note_qb, &$actor_qb, $note_criteria, $actor_criteria]); diff --git a/plugins/DeleteNote/DeleteNote.php b/plugins/DeleteNote/DeleteNote.php index b85a6d19fb..f1e666b512 100644 --- a/plugins/DeleteNote/DeleteNote.php +++ b/plugins/DeleteNote/DeleteNote.php @@ -22,6 +22,7 @@ declare(strict_types = 1); namespace Plugin\DeleteNote; use ActivityPhp\Type\AbstractObject; +use App\Core\Cache; use App\Core\DB\DB; use App\Core\Event; use function App\Core\I18n\_m; @@ -51,6 +52,14 @@ use Symfony\Component\HttpFoundation\Request; */ class DeleteNote extends NoteHandlerPlugin { + public static function cacheKeys(int|Note $note_id): array + { + $note_id = \is_int($note_id) ? $note_id : $note_id->getId(); + return [ + 'activity' => "deleted-note-activity-{$note_id}", + ]; + } + /** * **Checks actor permissions for the DeleteNote action, deletes given Note * and creates respective Activity and Notification** @@ -85,6 +94,7 @@ class DeleteNote extends NoteHandlerPlugin // Undertaker believes the actor can terminate this note $activity = $note->delete(actor: $actor, source: 'web'); + Cache::delete(self::cacheKeys($note)['activity']); // Undertaker successful Event::handle('NewNotification', [$actor, $activity, [], _m('{nickname} deleted note {note_id}.', ['nickname' => $actor->getNickname(), 'note_id' => $activity->getObjectId()])]); @@ -106,8 +116,13 @@ class DeleteNote extends NoteHandlerPlugin { $actor = \is_int($actor) ? Actor::getById($actor) : $actor; $note = \is_int($note) ? Note::getById($note) : $note; - // Try and find if note was already deleted - if (\is_null(DB::findOneBy(Activity::class, ['verb' => 'delete', 'object_type' => 'note', 'object_id' => $note->getId()], return_null: true))) { + // Try to find if note was already deleted + if (\is_null( + Cache::get( + self::cacheKeys($note)['activity'], + fn () => DB::findOneBy(Activity::class, ['verb' => 'delete', 'object_type' => 'note', 'object_id' => $note->getId()], return_null: true), + ), + )) { // If none found, then undertaker has a job to do return self::undertaker($actor, $note); } else { @@ -145,8 +160,12 @@ class DeleteNote extends NoteHandlerPlugin if (\is_null($actor = Common::actor())) { return Event::next; } - // Only add action if note wasn't already deleted! - if (\is_null(DB::findOneBy(Activity::class, ['verb' => 'delete', 'object_type' => 'note', 'object_id' => $note->getId()], return_null: true)) + if ( + // Only add action if note wasn't already deleted! + \is_null(Cache::get( + self::cacheKeys($note)['activity'], + fn () => DB::findOneBy(Activity::class, ['verb' => 'delete', 'object_type' => 'note', 'object_id' => $note->getId()], return_null: true), + )) // And has permissions && $actor->canAdmin($note->getActor())) { $delete_action_url = Router::url('delete_note_action', ['note_id' => $note->getId()]); diff --git a/plugins/Favourite/Entity/NoteFavourite.php b/plugins/Favourite/Entity/NoteFavourite.php index 6a30cd0c3a..de3027f23a 100644 --- a/plugins/Favourite/Entity/NoteFavourite.php +++ b/plugins/Favourite/Entity/NoteFavourite.php @@ -21,8 +21,11 @@ declare(strict_types = 1); namespace Plugin\Favourite\Entity; +use App\Core\Cache; use App\Core\DB\DB; use App\Core\Entity; +use App\Entity\Actor; +use App\Entity\LocalUser; use App\Entity\Note; use DateTimeInterface; @@ -82,22 +85,39 @@ class NoteFavourite extends Entity // @codeCoverageIgnoreEnd // }}} Autocode + public static function cacheKeys(int|Note $note_id, int|Actor|LocalUser|null $actor_id = null): array + { + $note_id = \is_int($note_id) ? $note_id : $note_id->getId(); + $actor_id = \is_null($actor_id) ? null : (\is_int($actor_id) ? $actor_id : $actor_id->getId()); + return [ + 'favourite' => "note-favourite-{$note_id}-{$actor_id}", + 'favourites' => "note-favourites-{$note_id}", + 'favourites-actors' => "note-favourites-actors-{$note_id}", + ]; + } + public static function getNoteFavourites(Note $note): array { - return DB::findBy('note_favourite', ['note_id' => $note->getId()]); + return Cache::getList( + self::cacheKeys($note)['favourites'], + fn () => DB::findBy('note_favourite', ['note_id' => $note->getId()]), + ); } public static function getNoteFavouriteActors(Note $note): array { - return DB::dql( - <<<'EOF' - select a from actor as a - inner join note_favourite as nf - with nf.note_id = :note_id - where a.id = nf.actor_id - order by nf.created DESC - EOF, - ['note_id' => $note->getId()], + return Cache::getList( + self::cacheKeys($note)['favourites-actors'], + fn () => DB::dql( + <<<'EOF' + select a from actor a + inner join note_favourite nf + with a.id = nf.actor_id + where nf.note_id = :note_id + order by nf.created DESC + EOF, + ['note_id' => $note->getId()], + ), ); } diff --git a/plugins/Favourite/Favourite.php b/plugins/Favourite/Favourite.php index 6da9e5b6d7..2952eebaad 100644 --- a/plugins/Favourite/Favourite.php +++ b/plugins/Favourite/Favourite.php @@ -23,6 +23,7 @@ declare(strict_types = 1); namespace Plugin\Favourite; +use App\Core\Cache; use App\Core\DB\DB; use App\Core\Event; use function App\Core\I18n\_m; @@ -56,10 +57,14 @@ class Favourite extends NoteHandlerPlugin public static function favourNote(int $note_id, int $actor_id, string $source = 'web'): ?Activity { $opts = ['note_id' => $note_id, 'actor_id' => $actor_id]; - $note_already_favoured = DB::findOneBy('note_favourite', $opts, return_null: true); - $activity = null; + $note_already_favoured = Cache::get( + FavouriteEntity::cacheKeys($note_id, $actor_id)['favourite'], + fn () => DB::findOneBy('note_favourite', $opts, return_null: true), + ); + $activity = null; if (\is_null($note_already_favoured)) { DB::persist(FavouriteEntity::create($opts)); + Cache::delete(FavouriteEntity::cacheKeys($note_id, $actor_id)['favourite']); $activity = Activity::create([ 'actor_id' => $actor_id, 'verb' => 'favourite', @@ -86,11 +91,15 @@ class Favourite extends NoteHandlerPlugin */ public static function unfavourNote(int $note_id, int $actor_id, string $source = 'web'): ?Activity { - $note_already_favoured = DB::findOneBy('note_favourite', ['note_id' => $note_id, 'actor_id' => $actor_id], return_null: true); - $activity = null; + $note_already_favoured = Cache::get( + FavouriteEntity::cacheKeys($note_id, $actor_id)['favourite'], + fn () => DB::findOneBy('note_favourite', ['note_id' => $note_id, 'actor_id' => $actor_id], return_null: true), + ); + $activity = null; if (!\is_null($note_already_favoured)) { DB::remove($note_already_favoured); - $favourite_activity = DB::findBy('activity', ['verb' => 'favourite', 'object_type' => 'note', 'object_id' => $note_id], order_by: ['created' => 'DESC'])[0]; + Cache::delete(FavouriteEntity::cacheKeys($note_id, $actor_id)['favourite']); + $favourite_activity = DB::findBy('activity', ['verb' => 'favourite', 'object_type' => 'note', 'actor_id' => $actor_id, 'object_id' => $note_id], order_by: ['created' => 'DESC'])[0]; $activity = Activity::create([ 'actor_id' => $actor_id, 'verb' => 'undo', // 'undo_favourite', @@ -123,7 +132,12 @@ class Favourite extends NoteHandlerPlugin // If note is favourite, "is_favourite" is 1 $opts = ['note_id' => $note->getId(), 'actor_id' => $user->getId()]; - $is_favourite = !\is_null(DB::findOneBy('note_favourite', $opts, return_null: true)); + $is_favourite = !\is_null( + Cache::get( + FavouriteEntity::cacheKeys($note->getId(), $user->getId())['favourite'], + fn () => DB::findOneBy('note_favourite', $opts, return_null: true), + ), + ); // Generating URL for favourite action route $args = ['id' => $note->getId()]; diff --git a/plugins/ProfileColor/Entity/ProfileColor.php b/plugins/ProfileColor/Entity/ProfileColor.php index f275c839b0..9ec6807ab9 100644 --- a/plugins/ProfileColor/Entity/ProfileColor.php +++ b/plugins/ProfileColor/Entity/ProfileColor.php @@ -103,6 +103,7 @@ class ProfileColor extends Entity // @codeCoverageIgnoreEnd // }}} Autocode + public static function schemaDef(): array { return [ diff --git a/plugins/ProfileColor/ProfileColor.php b/plugins/ProfileColor/ProfileColor.php index b1b6e0d831..75d9b6d025 100644 --- a/plugins/ProfileColor/ProfileColor.php +++ b/plugins/ProfileColor/ProfileColor.php @@ -27,7 +27,6 @@ use App\Core\DB\DB; use App\Core\Event; use App\Core\Modules\Plugin; use App\Core\Router\RouteLoader; -use App\Util\Exception\NotFoundException; use App\Util\Exception\RedirectException; use App\Util\Exception\ServerException; use App\Util\Formatting; @@ -84,16 +83,9 @@ class ProfileColor extends Plugin if ($actor !== null) { $actor_id = $actor->getId(); - try { - $profile_color_tab = Cache::get("profile-color-{$actor_id}", fn () => DB::findOneBy('profile_color', ['actor_id' => $actor_id])); - } catch (NotFoundException $e) { - return Event::next; - } - - $color = DB::findBy('profile_color', ['actor_id' => $actor_id])[0]; - if ($color !== null) { - $color = $color->getColor(); - $res[] = Formatting::twigRenderFile('/profileColor/profileColorView.html.twig', ['profile_color' => $profile_color_tab, 'actor' => $actor_id]); + $profile_color = Cache::get("profile-color-{$actor_id}", fn () => DB::findOneBy('profile_color', ['actor_id' => $actor_id], return_null: true)); + if (!\is_null($profile_color)) { + $res[] = Formatting::twigRenderFile('/profileColor/profileColorView.html.twig', ['profile_color' => $profile_color, 'actor' => $actor_id]); } } diff --git a/plugins/RepeatNote/Entity/NoteRepeat.php b/plugins/RepeatNote/Entity/NoteRepeat.php index 88476f0d18..05c9be0dc9 100644 --- a/plugins/RepeatNote/Entity/NoteRepeat.php +++ b/plugins/RepeatNote/Entity/NoteRepeat.php @@ -23,6 +23,7 @@ declare(strict_types = 1); namespace Plugin\RepeatNote\Entity; +use App\Core\Cache; use App\Core\DB\DB; use App\Core\Entity; use App\Entity\Note; @@ -39,6 +40,8 @@ use App\Entity\Note; */ class NoteRepeat extends Entity { + // {{{ Autocode + // @codeCoverageIgnoreStart private int $note_id; private int $actor_id; private int $repeat_of; @@ -75,26 +78,40 @@ class NoteRepeat extends Entity { return $this->repeat_of; } + // @codeCoverageIgnoreEnd + // }}} Autocode + + public static function cacheKeys(int|Note $note_id): array + { + $note_id = \is_int($note_id) ? $note_id : $note_id->getId(); + return [ + 'is_repeat' => "note-repeat-is-{$note_id}", + 'repeats' => "note-repeats-{$note_id}", + ]; + } /** * @return bool Returns true if Note provided is a repeat of another Note */ public static function isNoteRepeat(Note $note): bool { - return DB::count(self::class, ['note_id' => $note->getId()]) > 0; + return Cache::get(self::cacheKeys($note)['is_repeat'], fn () => DB::count(self::class, ['note_id' => $note->getId()]) > 0); } public static function getNoteRepeats(Note $note): array { - return DB::dql( - <<<'EOF' - select n from note as n - inner join note_repeat as nr - with nr.note_id = n.id - where nr.repeat_of = :note_id - order by n.created DESC, n.id DESC - EOF, - ['note_id' => $note->getId()], + return Cache::getList( + self::cacheKeys($note)['repeats'], + fn () => DB::dql( + <<<'EOF' + select n from note as n + inner join note_repeat as nr + with nr.note_id = n.id + where nr.repeat_of = :note_id + order by n.created DESC, n.id DESC + EOF, + ['note_id' => $note->getId()], + ), ); } diff --git a/plugins/RepeatNote/RepeatNote.php b/plugins/RepeatNote/RepeatNote.php index 547b3437a7..9a51432448 100644 --- a/plugins/RepeatNote/RepeatNote.php +++ b/plugins/RepeatNote/RepeatNote.php @@ -21,6 +21,7 @@ declare(strict_types = 1); namespace Plugin\RepeatNote; +use App\Core\Cache; use App\Core\DB\DB; use App\Core\Event; use function App\Core\I18n\_m; @@ -29,6 +30,7 @@ use App\Core\Router\RouteLoader; use App\Core\Router\Router; use App\Entity\Activity; use App\Entity\Actor; +use App\Entity\LocalUser; use App\Entity\Note; use App\Util\Common; use App\Util\Exception\BugFoundException; @@ -44,6 +46,15 @@ use Symfony\Component\HttpFoundation\Request; class RepeatNote extends NoteHandlerPlugin { + public static function cacheKeys(int|Note $note_id, int|Actor|LocalUser $actor_id): array + { + $note_id = \is_int($note_id) ? $note_id : $note_id->getId(); + $actor_id = \is_int($actor_id) ? $actor_id : $actor_id->getId(); + return [ + 'repeat' => "note-repeat-{$note_id}-{$actor_id}", + ]; + } + /** * **Repeats a Note** * @@ -63,12 +74,15 @@ class RepeatNote extends NoteHandlerPlugin */ public static function repeatNote(Note $note, int $actor_id, string $source = 'web'): ?Activity { - $repeat_entity = DB::findBy('note_repeat', [ - 'actor_id' => $actor_id, - 'note_id' => $note->getId(), - ])[0] ?? null; + $note_repeat = Cache::get( + self::cacheKeys($note->getId(), $actor_id)['repeat'], + fn () => DB::findOneBy('note_repeat', [ + 'actor_id' => $actor_id, + 'note_id' => $note->getId(), + ], return_null: true), + ); - if (!\is_null($repeat_entity)) { + if (!\is_null($note_repeat)) { return null; } @@ -99,6 +113,7 @@ class RepeatNote extends NoteHandlerPlugin 'actor_id' => $actor_id, 'repeat_of' => $original_note_id, ])); + Cache::delete(self::cacheKeys($note->getId(), $actor_id)['repeat']); // Log an activity $repeat_activity = Activity::create([ @@ -128,23 +143,30 @@ class RepeatNote extends NoteHandlerPlugin */ public static function unrepeatNote(int $note_id, int $actor_id, string $source = 'web'): ?Activity { - $already_repeated = DB::findBy(RepeatEntity::class, ['actor_id' => $actor_id, 'repeat_of' => $note_id])[0] ?? null; + $note_repeat = Cache::get( + self::cacheKeys($note_id, $actor_id)['repeat'], + fn () => DB::findOneBy('note_repeat', [ + 'actor_id' => $actor_id, + 'note_id' => $note_id, + ], return_null: true), + ); - if (!\is_null($already_repeated)) { // If it was repeated, then we can undo it + if (!\is_null($note_repeat)) { // If it was repeated, then we can undo it // Find previous repeat activity - $already_repeated_activity = DB::findBy(Activity::class, [ + $already_repeated_activity = DB::findOneBy(Activity::class, [ 'actor_id' => $actor_id, 'verb' => 'repeat', 'object_type' => 'note', - 'object_id' => $already_repeated->getRepeatOf(), - ])[0] ?? null; + 'object_id' => $note_repeat->getRepeatOf(), + ], return_null: true); // Remove the clone note - DB::findBy(Note::class, ['id' => $already_repeated->getNoteId()])[0]->delete(actor: Actor::getById($actor_id)); + DB::findOneBy(Note::class, ['id' => $note_repeat->getNoteId()])->delete(actor: Actor::getById($actor_id)); DB::flush(); // Remove from the note_repeat table - DB::remove(DB::findBy(RepeatEntity::class, ['note_id' => $already_repeated->getNoteId()])[0]); + DB::removeBy(RepeatEntity::class, ['note_id' => $note_repeat->getNoteId()]); + Cache::delete(self::cacheKeys($note_id, $actor_id)['repeat']); // Log an activity $undo_repeat_activity = Activity::create([ @@ -161,18 +183,18 @@ class RepeatNote extends NoteHandlerPlugin return $undo_repeat_activity; } else { // Either was undoed already - if (!\is_null($already_repeated_activity = DB::findBy('activity', [ + if (!\is_null($already_repeated_activity = DB::findOneBy('activity', [ 'actor_id' => $actor_id, 'verb' => 'repeat', 'object_type' => 'note', 'object_id' => $note_id, - ])[0] ?? null)) { - return DB::findBy('activity', [ + ], return_null: true))) { + return DB::findOneBy('activity', [ 'actor_id' => $actor_id, 'verb' => 'undo', 'object_type' => 'activity', 'object_id' => $already_repeated_activity->getId(), - ])[0] ?? null; // null if not undoed + ], return_null: true); // null if not undoed } else { // or it's an attempt to undo something that wasn't repeated in the first place, return null; @@ -216,14 +238,19 @@ class RepeatNote extends NoteHandlerPlugin return Event::next; } + $note_repeat = Cache::get( + self::cacheKeys($note->getId(), $user->getId())['repeat'], + fn () => DB::findOneBy('note_repeat', [ + 'actor_id' => $user->getId(), + 'note_id' => $note->getId(), + ], return_null: true), + ); + // If note is repeated, "is_repeated" is 1, 0 otherwise. - $is_repeat = ($note_repeat = DB::findBy('note_repeat', [ - 'actor_id' => $user->getId(), - 'repeat_of' => $note->getId(), - ])) !== [] ? 1 : 0; + $is_repeat = !\is_null($note_repeat); // Generating URL for repeat action route - $args = ['note_id' => $is_repeat === 0 ? $note->getId() : $note_repeat[0]->getRepeatOf()]; + $args = ['note_id' => !$is_repeat ? $note->getId() : $note_repeat->getRepeatOf()]; $type = Router::ABSOLUTE_PATH; $repeat_action_url = $is_repeat ? Router::url('repeat_remove', $args, $type) @@ -290,8 +317,8 @@ class RepeatNote extends NoteHandlerPlugin public function onNoteDeleteRelated(Note &$note, Actor $actor): bool { $note_repeats_list = RepeatEntity::getNoteRepeats($note); - foreach ($note_repeats_list as $repeat_entity) { - DB::remove($repeat_entity); + foreach ($note_repeats_list as $note_repeat) { + DB::remove($note_repeat); } return Event::next; diff --git a/plugins/WebMonetization/Entity/Wallet.php b/plugins/WebMonetization/Entity/Wallet.php index 8310fa3940..765601f2ea 100644 --- a/plugins/WebMonetization/Entity/Wallet.php +++ b/plugins/WebMonetization/Entity/Wallet.php @@ -50,6 +50,7 @@ class Wallet extends Entity // @codeCoverageIgnoreEnd // }}} Autocode + public static function schemaDef() { return [ diff --git a/plugins/WebMonetization/Entity/WebMonetization.php b/plugins/WebMonetization/Entity/WebMonetization.php index 45600866c9..27b4df9949 100644 --- a/plugins/WebMonetization/Entity/WebMonetization.php +++ b/plugins/WebMonetization/Entity/WebMonetization.php @@ -74,6 +74,7 @@ class WebMonetization extends Entity // @codeCoverageIgnoreEnd // }}} Autocode + public function getNotificationTargetIds(array $ids_already_known = [], ?int $sender_id = null, bool $include_additional = true): array { if (\array_key_exists('object', $ids_already_known)) { @@ -88,6 +89,7 @@ class WebMonetization extends Entity } return $target_ids; } + public static function schemaDef() { return [ diff --git a/plugins/WebMonetization/WebMonetization.php b/plugins/WebMonetization/WebMonetization.php index f561eef91b..6abff02090 100644 --- a/plugins/WebMonetization/WebMonetization.php +++ b/plugins/WebMonetization/WebMonetization.php @@ -31,12 +31,14 @@ declare(strict_types = 1); namespace Plugin\WebMonetization; +use App\Core\Cache; use App\Core\DB\DB; use App\Core\Event; use App\Core\Form; use function App\Core\I18n\_m; use App\Core\Modules\Plugin; use App\Entity\Activity; +use App\Entity\Actor; use App\Entity\LocalUser; use App\Util\Common; use App\Util\Exception\RedirectException; @@ -195,21 +197,40 @@ class WebMonetization extends Plugin } return Event::next; } + + public static function cacheKeys(int|LocalUser|Actor $id): array + { + if (!\is_int($id)) { + $id = $id->getId(); + } + return [ + 'wallets' => "webmonetization-wallets-sender-{$id}", + ]; + } + public function onAppendToHead(Request $request, &$res): bool { - $user = Common::actor(); + $user = Common::user(); if (\is_null($user)) { return Event::next; } + // donate to everyone! // Using Javascript, it can be improved to donate only // to actors owning notes rendered on current page. - $entries = DB::dql(<<<'EOF' - SELECT wallet FROM \Plugin\WebMonetization\Entity\Wallet wallet - INNER JOIN \Plugin\WebMonetization\Entity\WebMonetization wm - WITH wallet.actor_id = wm.receiver - WHERE wm.active = :active AND wm.sender = :sender - EOF, ['sender' => $user->getId(), 'active' => true]); + $entries = Cache::getList( + self::cacheKeys($user->getId())['wallets'], + fn () => DB::dql( + <<<'EOF' + SELECT wallet FROM webmonetizationWallet wallet + INNER JOIN webmonetization wm + WITH wallet.actor_id = wm.receiver + WHERE wm.active = :active AND wm.sender = :sender + EOF, + ['sender' => $user->getId(), 'active' => true], + ), + ); + foreach ($entries as $entry) { $res[] = Formatting::twigRenderString( '',