Update StoryService and add has_story to AccountTransformer

pull/7183/head
Daniel Supernault 2 weeks ago
parent 7975ba9c75
commit bbd7618c46
No known key found for this signature in database
GPG Key ID: 23740873EE6F76A1

@ -61,7 +61,8 @@ class AccountService
$account['location'],
$account['note_text'],
$account['pronouns'],
$account['website']
$account['website'],
$account['has_story'],
);
$account['avatar_static'] = $account['avatar'];

@ -706,4 +706,60 @@ class StoryIndexService
{
return config('database.redis.client') === 'predis' ? ['withscores' => true] : true;
}
public function hasActiveStory(int $profileId): bool
{
return $this->activeStoryCount($profileId) > 0;
}
public function activeStoryCount(int $profileId): int
{
$min = '('.(time() - self::STORY_TTL);
return $this->redisInt(fn () => Redis::zcount($this->authorKey($profileId), $min, '+inf'));
}
/**
* Newest live story id for an author, or null. Same window as above.
*/
public function latestStoryId(int $profileId): ?int
{
$min = '('.(time() - self::STORY_TTL);
$ids = $this->redisArray(fn () => Redis::zrevrangebyscore(
$this->authorKey($profileId),
'+inf',
$min,
['limit' => [0, 1]]
));
return $ids ? (int) $ids[0] : null;
}
/**
* Story ids the viewer has seen from this author. Empty if nothing is
* recorded (or the seen key already expired with the stories).
*/
public function seenStoryIds(int $viewerId, int $authorId): array
{
return $this->redisArray(fn () => Redis::smembers($this->seenKey($viewerId, $authorId)));
}
/**
* True when the index positively knows the viewer saw this story.
* False means "not recorded here", not "definitely unseen".
*/
public function hasSeen(int $viewerId, int $storyId): bool
{
$authorId = Redis::hget($this->storyKey($storyId), 'profile_id');
if (! $authorId) {
return false;
}
return $this->redisBool(fn () => Redis::sismember(
$this->seenKey($viewerId, (int) $authorId),
(string) $storyId
));
}
}

@ -45,26 +45,52 @@ class StoryService
public static function getStories($id, $pid = null)
{
return Story::whereProfileId($id)
$stories = Story::whereProfileId($id)
->where('active', true)
->where('expires_at', '>', now())
->latest()
->get()
->map(function ($s) use ($pid) {
->get();
if ($stories->isEmpty()) {
return [];
}
$seen = $pid
? self::seenLookup((int) $pid, (int) $id, $stories->pluck('id')->all())
: [];
return $stories
->map(function ($s) use ($seen) {
return [
'id' => (string) $s->id,
'type' => $s->type,
'duration' => 10,
'seen' => in_array($pid, self::views($s->id)),
'seen' => isset($seen[$s->id]),
'created_at' => $s->created_at->toAtomString(),
'expires_at' => $s->expires_at->toAtomString(),
'media' => url(Storage::url($s->path)),
'can_reply' => (bool) $s->can_reply,
'can_react' => (bool) $s->can_react,
'poll' => null, // 'poll' => $s->type == 'poll' ? PollService::storyPoll($s->id) : null, once PollService actually exists.
'poll' => null,
];
})
->toArray();
}
private static function seenLookup(int $viewerId, int $authorId, array $storyIds): array
{
$seen = app(StoryIndexService::class)->seenStoryIds($viewerId, $authorId);
if (empty($seen)) {
$seen = StoryView::whereProfileId($viewerId)
->whereIn('story_id', $storyIds)
->pluck('story_id')
->all();
}
return array_flip($seen);
}
public static function views($id)
{
return StoryView::whereStoryId($id)
@ -74,6 +100,10 @@ class StoryService
public static function hasSeen($pid, $sid)
{
if (app(StoryIndexService::class)->hasSeen((int) $pid, (int) $sid)) {
return true;
}
$key = self::STORY_KEY.'seen:'.$pid.':'.$sid;
return Cache::remember($key, 3600, function () use ($pid, $sid) {
@ -85,8 +115,15 @@ class StoryService
public static function latest($pid)
{
$id = app(StoryIndexService::class)->latestStoryId((int) $pid);
if ($id) {
return $id;
}
return Cache::remember(self::STORY_KEY.'latest:pid-'.$pid, 3600, function () use ($pid) {
$story = Story::whereProfileId($pid)
->where('active', true)
->where('expires_at', '>', now())
->latest()
->first();
@ -97,6 +134,7 @@ class StoryService
public static function delLatest($pid)
{
Cache::forget(self::STORY_KEY.'latest:pid-'.$pid);
AccountService::del($pid);
return Cache::forget('pf:stories:recent-self:'.$pid);
}
@ -152,17 +190,19 @@ class StoryService
public static function reactIncrement($storyId, $profileId)
{
$key = 'pf:stories:react-counter:storyid-'.$storyId.':profileid-'.$profileId;
if (Redis::get($key) == null) {
Redis::setex($key, 86400, 1);
} else {
return Redis::incr($key);
$count = (int) Redis::incr($key);
if ($count === 1) {
Redis::expire($key, 86400);
}
return $count;
}
public static function reactCounter($storyId, $profileId)
{
$key = 'pf:stories:react-counter:storyid-'.$storyId.':profileid-'.$profileId;
return (int) Redis::get($key) ?? 0;
return (int) (Redis::get($key) ?? 0);
}
}

@ -6,6 +6,7 @@ use App\Models\Profile;
use App\Models\User;
use App\Models\UserSetting;
use App\Services\PronounService;
use App\Services\StoryIndexService;
use Illuminate\Support\Facades\Cache;
use League\Fractal;
@ -71,6 +72,7 @@ class AccountTransformer extends Fractal\TransformerAbstract
'last_fetched_at' => $local ? null : $profile->last_fetched_at?->toJSON(),
'pronouns' => PronounService::get($profile->id),
'location' => $profile->location,
'has_story' => app(StoryIndexService::class)->hasActiveStory($profile->id),
];
$moved = $this->resolveMoved($profile);

Loading…
Cancel
Save