Add YouTube API key testing in settings
Allow users to test their YouTube API key directly from the settings page by clicking a button. Uses a LiveView component that provides visual feedback on whether the key is valid. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
67d8bd5598
commit
dcee984567
4 changed files with 120 additions and 9 deletions
|
|
@ -23,6 +23,39 @@ defmodule Pinchflat.FastIndexing.YoutubeApi do
|
||||||
@impl YoutubeBehaviour
|
@impl YoutubeBehaviour
|
||||||
def enabled?, do: Enum.any?(api_keys())
|
def enabled?, do: Enum.any?(api_keys())
|
||||||
|
|
||||||
|
@doc """
|
||||||
|
Tests if a YouTube API key is valid by making a simple API request.
|
||||||
|
|
||||||
|
Returns :ok | {:error, binary()}
|
||||||
|
"""
|
||||||
|
@impl YoutubeBehaviour
|
||||||
|
def test_api_key(api_key) when is_binary(api_key) do
|
||||||
|
# Use a well-known public playlist (YouTube's "Popular Right Now" uploads playlist)
|
||||||
|
# to test if the API key is valid
|
||||||
|
test_playlist_id = "PLrAXtmErZgOeiKm4sgNOknGvNjby9efdf"
|
||||||
|
url = "https://youtube.googleapis.com/youtube/v3/playlistItems?part=id&maxResults=1&playlistId=#{test_playlist_id}&key=#{api_key}"
|
||||||
|
|
||||||
|
case http_client().get(url, accept: "application/json") do
|
||||||
|
{:ok, response} ->
|
||||||
|
case Phoenix.json_library().decode(response) do
|
||||||
|
{:ok, %{"error" => %{"message" => message}}} ->
|
||||||
|
{:error, message}
|
||||||
|
|
||||||
|
{:ok, %{"items" => _}} ->
|
||||||
|
:ok
|
||||||
|
|
||||||
|
{:ok, _} ->
|
||||||
|
:ok
|
||||||
|
|
||||||
|
{:error, _} ->
|
||||||
|
{:error, "Invalid JSON response"}
|
||||||
|
end
|
||||||
|
|
||||||
|
{:error, reason} ->
|
||||||
|
{:error, "Request failed: #{inspect(reason)}"}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Fetches the recent media IDs from the YouTube API for a given source.
|
Fetches the recent media IDs from the YouTube API for a given source.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -8,4 +8,5 @@ defmodule Pinchflat.FastIndexing.YoutubeBehaviour do
|
||||||
|
|
||||||
@callback enabled?() :: boolean()
|
@callback enabled?() :: boolean()
|
||||||
@callback get_recent_media_ids(%Source{}) :: {:ok, [String.t()]} | {:error, String.t()}
|
@callback get_recent_media_ids(%Source{}) :: {:ok, [String.t()]} | {:error, String.t()}
|
||||||
|
@callback test_api_key(String.t()) :: :ok | {:error, String.t()}
|
||||||
end
|
end
|
||||||
|
|
|
||||||
|
|
@ -32,15 +32,11 @@
|
||||||
Extractor Settings
|
Extractor Settings
|
||||||
</h3>
|
</h3>
|
||||||
|
|
||||||
<.input
|
{live_render(
|
||||||
field={f[:youtube_api_key]}
|
@conn,
|
||||||
placeholder="ABC123,DEF456"
|
Pinchflat.Settings.YoutubeApiKeyLive,
|
||||||
type="text"
|
session: %{"value" => f[:youtube_api_key].value}
|
||||||
label="YouTube API Key(s)"
|
)}
|
||||||
help={youtube_api_help()}
|
|
||||||
html_help={true}
|
|
||||||
inputclass="font-mono text-sm mr-4"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<.input
|
<.input
|
||||||
field={f[:extractor_sleep_interval_seconds]}
|
field={f[:extractor_sleep_interval_seconds]}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
defmodule Pinchflat.Settings.YoutubeApiKeyLive do
|
||||||
|
use PinchflatWeb, :live_view
|
||||||
|
|
||||||
|
alias PinchflatWeb.Settings.SettingHTML
|
||||||
|
|
||||||
|
def render(assigns) do
|
||||||
|
~H"""
|
||||||
|
<.input
|
||||||
|
type="text"
|
||||||
|
id="setting_youtube_api_key"
|
||||||
|
name="setting[youtube_api_key]"
|
||||||
|
value={@value}
|
||||||
|
label="YouTube API Key(s)"
|
||||||
|
help={SettingHTML.youtube_api_help()}
|
||||||
|
html_help={true}
|
||||||
|
inputclass="font-mono text-sm mr-4"
|
||||||
|
placeholder="ABC123,DEF456"
|
||||||
|
phx-change="youtube_api_key_changed"
|
||||||
|
>
|
||||||
|
<:input_append>
|
||||||
|
<.icon_button icon_name={@icon_name} class="h-12 w-12" phx-click="test_youtube_api_key" tooltip={@tooltip} />
|
||||||
|
</:input_append>
|
||||||
|
</.input>
|
||||||
|
"""
|
||||||
|
end
|
||||||
|
|
||||||
|
def mount(_params, session, socket) do
|
||||||
|
new_assigns = %{
|
||||||
|
value: session["value"],
|
||||||
|
icon_name: "hero-play",
|
||||||
|
tooltip: "Test API Key"
|
||||||
|
}
|
||||||
|
|
||||||
|
{:ok, assign(socket, new_assigns)}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_event("test_youtube_api_key", _params, %{assigns: assigns} = socket) do
|
||||||
|
case test_api_key(assigns.value) do
|
||||||
|
:ok ->
|
||||||
|
Process.send_after(self(), :reset_button_icon, 4_000)
|
||||||
|
{:noreply, assign(socket, %{icon_name: "hero-check", tooltip: "Success!"})}
|
||||||
|
|
||||||
|
{:error, reason} ->
|
||||||
|
Process.send_after(self(), :reset_button_icon, 4_000)
|
||||||
|
{:noreply, assign(socket, %{icon_name: "hero-x-mark", tooltip: reason})}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_event("youtube_api_key_changed", %{"setting" => setting}, socket) do
|
||||||
|
{:noreply, assign(socket, %{value: setting["youtube_api_key"]})}
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_info(:reset_button_icon, socket) do
|
||||||
|
{:noreply, assign(socket, %{icon_name: "hero-play", tooltip: "Test API Key"})}
|
||||||
|
end
|
||||||
|
|
||||||
|
defp test_api_key(nil), do: {:error, "No API key provided"}
|
||||||
|
defp test_api_key(""), do: {:error, "No API key provided"}
|
||||||
|
|
||||||
|
defp test_api_key(keys_string) do
|
||||||
|
# Test the first API key provided
|
||||||
|
first_key =
|
||||||
|
keys_string
|
||||||
|
|> String.split(",")
|
||||||
|
|> Enum.map(&String.trim/1)
|
||||||
|
|> Enum.reject(&(&1 == ""))
|
||||||
|
|> List.first()
|
||||||
|
|
||||||
|
case first_key do
|
||||||
|
nil ->
|
||||||
|
{:error, "No API key provided"}
|
||||||
|
|
||||||
|
key ->
|
||||||
|
youtube_api().test_api_key(key)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp youtube_api do
|
||||||
|
Application.get_env(:pinchflat, :youtube_api, Pinchflat.FastIndexing.YoutubeApi)
|
||||||
|
end
|
||||||
|
end
|
||||||
Loading…
Reference in a new issue