Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
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
51 changes: 51 additions & 0 deletions migrations/Version20260526175316.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
<?php

declare(strict_types=1);

namespace DoctrineMigrations;

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

final class Version20260526175316 extends AbstractMigration
{
public function getDescription(): string
{
return 'adds last_boosted_at with default value to entry, entry_comment, post and post_comment';
}

public function up(Schema $schema): void
{
$this->addSql('ALTER TABLE entry ADD last_boosted_at TIMESTAMP(0) WITH TIME ZONE NOT NULL DEFAULT to_timestamp(0)::date');
$this->addSql('CREATE INDEX entry_last_boosted_at_idx ON entry (last_boosted_at)');
$this->addSql('ALTER TABLE entry_comment ADD last_boosted_at TIMESTAMP(0) WITH TIME ZONE NOT NULL DEFAULT to_timestamp(0)::date');
$this->addSql('CREATE INDEX entry_comment_last_boosted_at_idx ON entry_comment (last_boosted_at)');
$this->addSql('ALTER TABLE post ADD last_boosted_at TIMESTAMP(0) WITH TIME ZONE NOT NULL DEFAULT to_timestamp(0)::date');
$this->addSql('CREATE INDEX post_last_boosted_at_idx ON post (last_boosted_at)');
$this->addSql('ALTER TABLE post_comment ADD last_boosted_at TIMESTAMP(0) WITH TIME ZONE NOT NULL DEFAULT to_timestamp(0)::date');
$this->addSql('CREATE INDEX post_comment_last_boosted_at_idx ON post_comment (last_boosted_at)');
}

public function down(Schema $schema): void
{
$this->addSql('DROP INDEX entry_last_boosted_at_idx');
$this->addSql('ALTER TABLE entry DROP last_boosted_at');
$this->addSql('DROP INDEX entry_comment_last_boosted_at_idx');
$this->addSql('ALTER TABLE entry_comment DROP last_boosted_at');
$this->addSql('DROP INDEX post_last_boosted_at_idx');
$this->addSql('ALTER TABLE post DROP last_boosted_at');
$this->addSql('DROP INDEX post_comment_last_boosted_at_idx');
$this->addSql('ALTER TABLE post_comment DROP last_boosted_at');
}

public function postUp(Schema $schema): void
{
$this->connection->transactional(function (): void {
$sqlTpl = 'UPDATE $e SET last_boosted_at = greatest((SELECT $e_vote.created_at FROM $e_vote WHERE $e_vote.$fk = $e.id ORDER BY $e_vote.created_at DESC LIMIT 1), created_at);';
$this->connection->executeStatement(str_replace('$e', 'entry', str_replace('$fk', 'entry_id', $sqlTpl)));
$this->connection->executeStatement(str_replace('$e', 'entry_comment', str_replace('$fk', 'comment_id', $sqlTpl)));
$this->connection->executeStatement(str_replace('$e', 'post', str_replace('$fk', 'post_id', $sqlTpl)));
$this->connection->executeStatement(str_replace('$e', 'post_comment', str_replace('$fk', 'comment_id', $sqlTpl)));
});
}
}
2 changes: 2 additions & 0 deletions src/Entity/Contracts/VotableInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,6 @@
public function getUserChoice(User $user): int;

public function getUserVote(User $user): ?Vote;

public function updateLastBoostDate(): self;

Check failure

Code scanning / Psalm

MissingAbstractPureAnnotation Error

updateLastBoostDate must be marked with one of @psalm-pure, @psalm-mutation-free, @psalm-external-mutation-free, @psalm-impure to aid security analysis

Check failure

Code scanning / Psalm

PossiblyUnusedReturnValue Error

The return value for this method is never used
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
}
4 changes: 3 additions & 1 deletion src/Entity/Entry.php
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
#[Index(columns: ['comment_count'], name: 'entry_comment_count_idx')]
#[Index(columns: ['created_at'], name: 'entry_created_at_idx')]
#[Index(columns: ['last_active'], name: 'entry_last_active_at_idx')]
#[Index(columns: ['last_boosted_at'], name: 'entry_last_boosted_at_idx')]
#[Index(columns: ['body_ts'], name: 'entry_body_ts_idx')]
#[Index(columns: ['title_ts'], name: 'entry_title_ts_idx')]
class Entry implements VotableInterface, CommentInterface, DomainInterface, VisibilityInterface, RankingInterface, ReportInterface, FavouriteInterface, ActivityPubActivityInterface, ContentVisibilityInterface
Expand Down Expand Up @@ -173,8 +174,9 @@
$user->addEntry($this);

$this->createdAtTraitConstruct();

$this->updateLastActive();

$this->lastBoostedAt = $this->createdAt;

Check failure

Code scanning / Psalm

UninitializedProperty Error

Cannot use uninitialized property $this->createdAt
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
}

public function updateLastActive(): void
Expand Down
3 changes: 3 additions & 0 deletions src/Entity/EntryComment.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
#[Index(columns: ['up_votes'], name: 'entry_comment_up_votes_idx')]
#[Index(columns: ['last_active'], name: 'entry_comment_last_active_at_idx')]
#[Index(columns: ['created_at'], name: 'entry_comment_created_at_idx')]
#[Index(columns: ['last_boosted_at'], name: 'entry_comment_last_boosted_at_idx')]
#[Index(columns: ['body_ts'], name: 'entry_comment_body_ts_idx')]
class EntryComment implements VotableInterface, VisibilityInterface, ReportInterface, FavouriteInterface, ActivityPubActivityInterface
{
Expand Down Expand Up @@ -127,6 +128,8 @@

$this->createdAtTraitConstruct();
$this->updateLastActive();

$this->lastBoostedAt = $this->createdAt;

Check failure

Code scanning / Psalm

UninitializedProperty Error

Cannot use uninitialized property $this->createdAt
Comment thread
github-advanced-security[bot] marked this conversation as resolved.
Fixed
}

public function updateLastActive(): void
Expand Down
3 changes: 3 additions & 0 deletions src/Entity/Post.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
#[Index(columns: ['comment_count'], name: 'post_comment_count_idx')]
#[Index(columns: ['created_at'], name: 'post_created_at_idx')]
#[Index(columns: ['last_active'], name: 'post_last_active_at_idx')]
#[Index(columns: ['last_boosted_at'], name: 'post_last_boosted_at_idx')]
#[Index(columns: ['body_ts'], name: 'post_body_ts_idx')]
class Post implements VotableInterface, CommentInterface, VisibilityInterface, RankingInterface, ReportInterface, FavouriteInterface, ActivityPubActivityInterface
{
Expand Down Expand Up @@ -127,6 +128,8 @@ public function __construct(

$this->createdAtTraitConstruct();
$this->updateLastActive();

$this->lastBoostedAt = $this->createdAt;
}

public function updateLastActive(): void
Expand Down
3 changes: 3 additions & 0 deletions src/Entity/PostComment.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
#[Index(columns: ['up_votes'], name: 'post_comment_up_votes_idx')]
#[Index(columns: ['last_active'], name: 'post_comment_last_active_at_idx')]
#[Index(columns: ['created_at'], name: 'post_comment_created_at_idx')]
#[Index(columns: ['last_boosted_at'], name: 'post_comment_last_boosted_at_idx')]
#[Index(columns: ['body_ts'], name: 'post_comment_body_ts_idx')]
class PostComment implements VotableInterface, VisibilityInterface, ReportInterface, FavouriteInterface, ActivityPubActivityInterface
{
Expand Down Expand Up @@ -123,6 +124,8 @@ public function __construct(string $body, ?Post $post, User $user, ?PostComment

$this->createdAtTraitConstruct();
$this->updateLastActive();

$this->lastBoostedAt = $this->createdAt;
}

public function updateLastActive(): void
Expand Down
10 changes: 10 additions & 0 deletions src/Entity/Traits/VotableTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ trait VotableTrait
#[Column(type: 'integer', nullable: true)]
public ?int $apShareCount = null;

#[Column(type: 'datetimetz_immutable', nullable: false)]
public ?\DateTimeImmutable $lastBoostedAt;

public function countUpVotes(): int
{
return $this->apShareCount ?? $this->upVotes;
Expand Down Expand Up @@ -66,6 +69,13 @@ public function updateVoteCounts(): self
return $this;
}

public function updateLastBoostDate(): self
{
$this->lastBoostedAt = new \DateTimeImmutable();

return $this;
}

public function getUpVotes(): Collection
{
$this->votes->get(-1);
Expand Down
22 changes: 11 additions & 11 deletions src/Repository/ContentRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -461,17 +461,17 @@ private function getQueryAndParameters(Criteria $criteria, bool $addCursor): arr
// only join domain if we are explicitly looking at one
$domainJoin = $criteria->domain ? 'LEFT JOIN domain d ON d.id = c.domain_id' : '';

$entrySql = "SELECT c.id, 'entry' as type, c.type as content_type, c.created_at, c.ranking, c.score, c.comment_count, c.sticky, c.last_active, c.user_id FROM entry c
$entrySql = "SELECT c.id, 'entry' as type, c.type as content_type, c.created_at, c.last_boosted_at, c.ranking, c.score, c.comment_count, c.sticky, c.last_active, c.user_id FROM entry c
LEFT JOIN magazine m ON c.magazine_id = m.id
$domainJoin
$entryWhere";
$postSql = "SELECT c.id, 'post' as type, 'microblog' as content_type, c.created_at, c.ranking, c.score, c.comment_count, c.sticky, c.last_active, c.user_id FROM post c
$postSql = "SELECT c.id, 'post' as type, 'microblog' as content_type, c.created_at, c.last_boosted_at, c.ranking, c.score, c.comment_count, c.sticky, c.last_active, c.user_id FROM post c
LEFT JOIN magazine m ON c.magazine_id = m.id
$postWhere";
$entryCommentSql = "SELECT c.id, 'entry_comment' as type, 'microblog' as content_type, c.created_at, 0 as ranking, 0 as score, 0 as comment_count, false as sticky, c.last_active, c.user_id FROM entry_comment c
$entryCommentSql = "SELECT c.id, 'entry_comment' as type, 'microblog' as content_type, c.created_at, c.last_boosted_at, 0 as ranking, 0 as score, 0 as comment_count, false as sticky, c.last_active, c.user_id FROM entry_comment c
LEFT JOIN magazine m ON c.magazine_id = m.id
$entryCommentWhere";
$postCommentSql = "SELECT c.id, 'post_comment' as type, 'microblog' as content_type, c.created_at, 0 as ranking, 0 as score, 0 as comment_count, false as sticky, c.last_active, c.user_id FROM post_comment c
$postCommentSql = "SELECT c.id, 'post_comment' as type, 'microblog' as content_type, c.created_at, c.last_boosted_at, 0 as ranking, 0 as score, 0 as comment_count, false as sticky, c.last_active, c.user_id FROM post_comment c
LEFT JOIN magazine m ON c.magazine_id = m.id
$postCommentWhere";

Expand Down Expand Up @@ -523,7 +523,7 @@ private function getCursorFieldFromCriteria(Criteria $criteria): string
Criteria::SORT_HOT => 'ranking',
Criteria::SORT_COMMENTED => 'commentCount',
Criteria::SORT_ACTIVE => 'lastActive',
default => 'createdAt',
default => $criteria->includeBoosts ? 'lastBoostedAt' : 'createdAt',
};
}

Expand All @@ -534,8 +534,8 @@ private function getCursorWhereFromCriteria(Criteria $criteria): string
Criteria::SORT_HOT => 'c.ranking < :cursor',
Criteria::SORT_COMMENTED => 'c.comment_count < :cursor',
Criteria::SORT_ACTIVE => 'c.last_active < :cursor',
Criteria::SORT_OLD => 'c.created_at > :cursor',
default => 'c.created_at < :cursor',
Criteria::SORT_OLD => $criteria->includeBoosts ? 'c.last_boosted_at > :cursor' : 'c.created_at > :cursor',
default => $criteria->includeBoosts ? 'c.last_boosted_at < :cursor' : 'c.created_at < :cursor',
};
}

Expand All @@ -546,8 +546,8 @@ private function getCursorWhereInvertedFromCriteria(Criteria $criteria): string
Criteria::SORT_HOT => 'c.ranking > :cursor',
Criteria::SORT_COMMENTED => 'c.comment_count > :cursor',
Criteria::SORT_ACTIVE => 'c.last_active > :cursor',
Criteria::SORT_OLD => 'c.created_at < :cursor',
default => 'c.created_at >= :cursor',
Criteria::SORT_OLD => $criteria->includeBoosts ? 'c.last_boosted_at < :cursor' : 'c.created_at < :cursor',
default => $criteria->includeBoosts ? 'c.last_boosted_at >= :cursor' : 'c.created_at >= :cursor',
};
}

Expand Down Expand Up @@ -608,11 +608,11 @@ private function getOrderings(Criteria $criteria): array

switch ($criteria->sortOption) {
case Criteria::SORT_OLD:
$orderings[] = 'created_at ASC';
$orderings[] = $criteria->includeBoosts ? 'last_boosted_at ASC' : 'created_at ASC';
break;
case Criteria::SORT_NEW:
default:
$orderings[] = 'created_at DESC';
$orderings[] = $criteria->includeBoosts ? 'last_boosted_at DESC' : 'created_at DESC';
}

return $orderings;
Expand Down
2 changes: 2 additions & 0 deletions src/Service/VoteManager.php
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ public function vote(int $choice, VotableInterface $votable, User $user, $rateLi

if (VotableInterface::VOTE_UP === $choice && null !== $votable->apShareCount) {
++$votable->apShareCount;
$votable->updateLastBoostDate();
} elseif (VotableInterface::VOTE_DOWN === $choice && null !== $votable->apDislikeCount) {
++$votable->apDislikeCount;
}
Expand Down Expand Up @@ -132,6 +133,7 @@ public function upvote(VotableInterface $votable, User $user): Vote

$votable->updateVoteCounts();

$votable->updateLastBoostDate();
$votable->lastActive = new \DateTime();

if ($votable instanceof PostComment) {
Expand Down
Loading