From 3547b471ea39effe1d4b612b8276d600610f1359 Mon Sep 17 00:00:00 2001 From: Paulo Castellano Date: Tue, 16 Jun 2026 15:53:20 -0300 Subject: [PATCH] feat(discord): implement channel caching and enhance UI for channel selection - Introduced caching for Discord channel lookups, reducing API calls and improving performance. - Updated the DiscordSettings component to use a SearchableSelect for better user experience when selecting channels. - Added new language strings for channel search and no channels found messages in English, Spanish, and Portuguese. - Updated tests to ensure channel list caching is isolated between runs. --- app/Services/Social/Discord/DiscordClient.php | 41 +++++---- lang/en/posts.php | 2 + lang/es/posts.php | 2 + lang/pt-BR/posts.php | 2 + public/images/accounts/discord.png | Bin 2028 -> 14896 bytes resources/js/components/SearchableSelect.vue | 83 ++++++++++++++++++ .../posts/editor/DiscordSettings.vue | 19 ++-- tests/Feature/Discord/DiscordLookupTest.php | 2 + .../Services/Social/DiscordPublisherTest.php | 2 + 9 files changed, 128 insertions(+), 25 deletions(-) create mode 100644 resources/js/components/SearchableSelect.vue diff --git a/app/Services/Social/Discord/DiscordClient.php b/app/Services/Social/Discord/DiscordClient.php index 70e7b9b0..f7b3bbc9 100644 --- a/app/Services/Social/Discord/DiscordClient.php +++ b/app/Services/Social/Discord/DiscordClient.php @@ -8,6 +8,7 @@ use App\Services\Social\Concerns\HasSocialHttpClient; use Illuminate\Http\Client\PendingRequest; use Illuminate\Http\Client\Response; +use Illuminate\Support\Facades\Cache; /** * Read-side Discord API calls made with the global bot token (channel listing, @@ -22,6 +23,8 @@ class DiscordClient */ private const POSTABLE_CHANNEL_TYPES = [0, 5, 15]; + private const CHANNELS_TTL = 300; + public function baseUrl(): string { return (string) config('trypost.platforms.discord.api'); @@ -38,26 +41,32 @@ public function baseUrl(): string */ public function channels(string $guildId): array { - $response = $this->bot()->get("{$this->baseUrl()}/guilds/{$guildId}/channels"); + // Cached per guild for 5 minutes — channels change rarely and this runs + // both in the composer picker and on every publish (the channel guard), + // all against the shared, rate-limited bot token. A transient failure + // throws and is NOT cached, so the next call retries. + return Cache::remember("discord:channels:{$guildId}", self::CHANNELS_TTL, function () use ($guildId) { + $response = $this->bot()->get("{$this->baseUrl()}/guilds/{$guildId}/channels"); - if ($response->failed()) { - throw new PlatformUnavailableException("Discord channel lookup failed ({$response->status()}).", $response->status()); - } + if ($response->failed()) { + throw new PlatformUnavailableException("Discord channel lookup failed ({$response->status()}).", $response->status()); + } - $channels = $response->json(); + $channels = $response->json(); - if (! is_array($channels)) { - return []; - } + if (! is_array($channels)) { + return []; + } - return collect($channels) - ->filter(fn ($channel) => in_array((int) data_get($channel, 'type'), self::POSTABLE_CHANNEL_TYPES, true)) - ->map(fn ($channel) => [ - 'id' => (string) data_get($channel, 'id'), - 'name' => (string) data_get($channel, 'name'), - ]) - ->values() - ->all(); + return collect($channels) + ->filter(fn ($channel) => in_array((int) data_get($channel, 'type'), self::POSTABLE_CHANNEL_TYPES, true)) + ->map(fn ($channel) => [ + 'id' => (string) data_get($channel, 'id'), + 'name' => (string) data_get($channel, 'name'), + ]) + ->values() + ->all(); + }); } /** diff --git a/lang/en/posts.php b/lang/en/posts.php index 93781fce..6fe57b73 100644 --- a/lang/en/posts.php +++ b/lang/en/posts.php @@ -167,6 +167,8 @@ 'channel' => 'Channel', 'select_channel' => 'Select a channel', 'loading_channels' => 'Loading channels…', + 'search_channel' => 'Search channels…', + 'no_channels' => 'No channels found.', 'channel_required' => 'Select a Discord channel to publish this post.', 'mentions' => 'Mentions', 'search_mention' => 'Mention a role or member…', diff --git a/lang/es/posts.php b/lang/es/posts.php index 1537a9c0..c1da9111 100644 --- a/lang/es/posts.php +++ b/lang/es/posts.php @@ -167,6 +167,8 @@ 'channel' => 'Canal', 'select_channel' => 'Selecciona un canal', 'loading_channels' => 'Cargando canales…', + 'search_channel' => 'Buscar canales…', + 'no_channels' => 'No se encontraron canales.', 'channel_required' => 'Selecciona un canal de Discord para publicar este post.', 'mentions' => 'Menciones', 'search_mention' => 'Menciona un rol o miembro…', diff --git a/lang/pt-BR/posts.php b/lang/pt-BR/posts.php index 01f95f25..831b745e 100644 --- a/lang/pt-BR/posts.php +++ b/lang/pt-BR/posts.php @@ -167,6 +167,8 @@ 'channel' => 'Canal', 'select_channel' => 'Selecione um canal', 'loading_channels' => 'Carregando canais…', + 'search_channel' => 'Buscar canais…', + 'no_channels' => 'Nenhum canal encontrado.', 'channel_required' => 'Selecione um canal do Discord para publicar este post.', 'mentions' => 'Menções', 'search_mention' => 'Mencione um cargo ou membro…', diff --git a/public/images/accounts/discord.png b/public/images/accounts/discord.png index 92b7bde1d7323cdf7cfca58287ef677030a09489..90af762aabcb6d8a18ead0d012441e831611b3db 100644 GIT binary patch literal 14896 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 KMNUMnLSTaT2ATE% diff --git a/resources/js/components/SearchableSelect.vue b/resources/js/components/SearchableSelect.vue new file mode 100644 index 00000000..fdad0bb2 --- /dev/null +++ b/resources/js/components/SearchableSelect.vue @@ -0,0 +1,83 @@ + + + diff --git a/resources/js/components/posts/editor/DiscordSettings.vue b/resources/js/components/posts/editor/DiscordSettings.vue index 31a8c7e3..c141ea97 100644 --- a/resources/js/components/posts/editor/DiscordSettings.vue +++ b/resources/js/components/posts/editor/DiscordSettings.vue @@ -5,6 +5,7 @@ import { computed, onMounted, onUnmounted, ref, watch } from 'vue'; import { channels as channelsRoute, mentions as mentionsRoute } from '@/actions/App/Http/Controllers/App/DiscordController'; import InputError from '@/components/InputError.vue'; +import SearchableSelect from '@/components/SearchableSelect.vue'; import { Avatar } from '@/components/ui/avatar'; import { Input } from '@/components/ui/input'; import { usePageErrors } from '@/composables/usePageErrors'; @@ -97,6 +98,8 @@ const channelOptions = computed(() => { return channels.value; }); +const channelSelectOptions = computed(() => channelOptions.value.map((channel) => ({ value: channel.id, label: `#${channel.name}` }))); + const errors = usePageErrors(); const channelError = computed(() => { if (props.meta?.channel_id) { @@ -203,17 +206,15 @@ const updateEmbed = (index: number, patch: Partial) =>

{{ $t('posts.form.discord.channel') }}

- + :invalid="!!channelError" + />
diff --git a/tests/Feature/Discord/DiscordLookupTest.php b/tests/Feature/Discord/DiscordLookupTest.php index 400ac5eb..8792b9b1 100644 --- a/tests/Feature/Discord/DiscordLookupTest.php +++ b/tests/Feature/Discord/DiscordLookupTest.php @@ -6,9 +6,11 @@ use App\Models\SocialAccount; use App\Models\User; use App\Models\Workspace; +use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Http; beforeEach(function () { + Cache::flush(); // channel list is cached per guild — isolate between tests config(['trypost.platforms.discord.bot_token' => 'BOTTOKEN']); $this->user = User::factory()->create(); diff --git a/tests/Feature/Services/Social/DiscordPublisherTest.php b/tests/Feature/Services/Social/DiscordPublisherTest.php index 32bf2845..1b09727f 100644 --- a/tests/Feature/Services/Social/DiscordPublisherTest.php +++ b/tests/Feature/Services/Social/DiscordPublisherTest.php @@ -13,9 +13,11 @@ use App\Models\Workspace; use App\Services\Media\MediaOptimizer; use App\Services\Social\Discord\DiscordPublisher; +use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Http; beforeEach(function () { + Cache::flush(); // channel list is cached per guild — isolate between tests config(['trypost.platforms.discord.bot_token' => 'BOTTOKEN']); $this->user = User::factory()->create();