diff --git a/CHANGELOG.md b/CHANGELOG.md index 4d16f4c6a..101447674 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -68,6 +68,8 @@ - Update StoryIndexService, fix markSeen method ([e09291775](https://github.com/pixelfed/pixelfed/commit/e09291775)) - Update StoryComposeController, add StoryIndexService support ([6c701b335](https://github.com/pixelfed/pixelfed/commit/6c701b335)) - Update StoryIndexService, improve predis + phpredis support ([564d8d109](https://github.com/pixelfed/pixelfed/commit/564d8d109)) +- Update StoryApiV1Controller, add missing validation rule ([76d9ded69](https://github.com/pixelfed/pixelfed/commit/76d9ded69)) +- Update StoryIndexService, improve predis/phpredis support ([53b74bf16](https://github.com/pixelfed/pixelfed/commit/53b74bf16)) - ([](https://github.com/pixelfed/pixelfed/commit/)) ## [v0.12.5 (2025-03-23)](https://github.com/pixelfed/pixelfed/compare/v0.12.5...dev) diff --git a/app/Http/Controllers/Stories/StoryApiV1Controller.php b/app/Http/Controllers/Stories/StoryApiV1Controller.php index 44fdb062d..2658e060b 100644 --- a/app/Http/Controllers/Stories/StoryApiV1Controller.php +++ b/app/Http/Controllers/Stories/StoryApiV1Controller.php @@ -399,8 +399,9 @@ class StoryApiV1Controller extends Controller 'overlays.*.absoluteY' => 'numeric', 'overlays.*.color' => 'hex_color', 'overlays.*.backgroundColor' => 'string|in:transparent,#FFFFFF,#000000', - 'overlays.*.content' => 'string|min:1|max:80', - 'overlays.*.fontSize' => 'numeric|min:10|max:80', + 'overlays.*.content' => 'string|min:1|max:250', + 'overlays.*.fontSize' => 'numeric|min:10|max:180', + 'overlays.*.fontFamily' => 'string|in:default,serif,mono,rounded,bold', 'overlays.*.rotation' => 'numeric|min:-360|max:360', 'overlays.*.scale' => 'numeric|min:0.1|max:5', 'overlays.*.x' => 'numeric', diff --git a/app/Services/StoryIndexService.php b/app/Services/StoryIndexService.php index 4a5bfd66e..a6cf26c42 100644 --- a/app/Services/StoryIndexService.php +++ b/app/Services/StoryIndexService.php @@ -99,6 +99,52 @@ class StoryIndexService return $this->redisToBool($command()); } + /** + * Safely convert Redis result to array, handling both predis and phpredis + */ + private function redisToArray($value): array + { + if (is_array($value)) { + return $value; + } + + if (is_null($value)) { + return []; + } + + // Handle phpredis object returns that might be iterable + if (is_object($value)) { + if (method_exists($value, 'toArray')) { + return $value->toArray(); + } + + if ($value instanceof \Iterator || $value instanceof \IteratorAggregate) { + return iterator_to_array($value); + } + + if (method_exists($value, '__toString')) { + $str = $value->__toString(); + + return $str ? [$str] : []; + } + } + + // Handle single values + if (is_string($value) || is_numeric($value)) { + return [$value]; + } + + return []; + } + + /** + * Safely execute Redis commands that return arrays + */ + private function redisArray(callable $command): array + { + return $this->redisToArray($command()); + } + public function indexStory($story): void { if (! $story->active) { @@ -283,7 +329,7 @@ class StoryIndexService private function clearStoryCache(): void { - $storyKeys = Redis::keys('story:*'); + $storyKeys = $this->redisArray(fn () => Redis::keys('story:*')); $storyKeys = array_filter($storyKeys, function ($key) { return ! str_contains($key, 'following:'); }); @@ -348,7 +394,7 @@ class StoryIndexService $authorIds = []; if ($followingCount > 1500) { - $active = Redis::smembers('story:active_authors'); + $active = $this->redisArray(fn () => Redis::smembers('story:active_authors')); if ($active) { $results = Redis::pipeline(function ($pipe) use ($active, $pid) { foreach ($active as $aid) { @@ -367,7 +413,7 @@ class StoryIndexService } } } else { - $authorIds = Redis::sinter("following:{$pid}", 'story:active_authors'); + $authorIds = $this->redisArray(fn () => Redis::sinter("following:{$pid}", 'story:active_authors')); } if ($this->redisInt(fn () => Redis::zcard($this->authorKey($pid))) > 0) { @@ -379,16 +425,18 @@ class StoryIndexService return []; } - $responses = Redis::pipeline(function ($pipe) use ($authorIds, $opt) { + $responses = $this->redisArray(fn () => Redis::pipeline(function ($pipe) use ($authorIds, $opt) { foreach ($authorIds as $aid) { $pipe->zrevrange("story:by_author:{$aid}", 0, -1, $opt); } - }); + })); $authorToStoryIds = []; $authorLatestTs = []; foreach ($authorIds as $i => $aid) { $withScores = $responses[$i] ?? []; + // Ensure withScores is also an array + $withScores = $this->redisToArray($withScores); $authorToStoryIds[$aid] = array_keys($withScores); $authorLatestTs[$aid] = $withScores ? (float) array_values($withScores)[0] : 0.0; } @@ -401,7 +449,7 @@ class StoryIndexService $storyMap = []; foreach ($allStoryIds as $sid) { - $h = Redis::hgetall($this->storyKey($sid)); + $h = $this->redisArray(fn () => Redis::hgetall($this->storyKey($sid))); if (! empty($h)) { $storyMap[$sid] = $h; } @@ -409,10 +457,12 @@ class StoryIndexService $seenCache = []; foreach ($authorIds as $aid) { - $seenCache[$aid] = Redis::smembers($this->seenKey($viewerId, (int) $aid)) ?: []; + $seenCache[$aid] = $this->redisArray(fn () => Redis::smembers($this->seenKey($viewerId, (int) $aid))); $seenCache[$aid] = array_flip($seenCache[$aid]); } + // Ensure $authorIds is always an array before using array_map + $authorIds = $this->redisToArray($authorIds); $profiles = $profileHydrator(array_map('intval', $authorIds)); $nodes = [];