Skip to content

Commit f1e52b0

Browse files
mjcCopilot
andcommitted
Sync managed Arr webhooks
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 38384da commit f1e52b0

8 files changed

Lines changed: 517 additions & 17 deletions

File tree

config/test.exs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ config :phoenix_live_view,
4444

4545
# Mark test environment so async DB tasks run synchronously
4646
config :reencodarr, env: :test
47+
config :reencodarr, auto_reconcile_managed_webhooks: false
4748

4849
# Don't start Erlang distribution in tests — avoids duplicate_name conflicts
4950
# with a running dev node and unnecessary epmd registration per test run.

lib/reencodarr/application.ex

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ defmodule Reencodarr.Application do
44
@moduledoc false
55

66
use Application
7+
alias Reencodarr.Services.WebhookSync
78

89
@impl true
910
def start(_type, _args) do
@@ -17,7 +18,11 @@ defmodule Reencodarr.Application do
1718
maybe_start_distribution()
1819

1920
opts = [strategy: :one_for_one, name: Reencodarr.Supervisor]
20-
Supervisor.start_link(children(), opts)
21+
22+
with {:ok, pid} <- Supervisor.start_link(children(), opts) do
23+
maybe_start_webhook_sync()
24+
{:ok, pid}
25+
end
2126
end
2227

2328
defp setup_file_logging do
@@ -149,6 +154,12 @@ defmodule Reencodarr.Application do
149154
end
150155
end
151156

157+
defp maybe_start_webhook_sync do
158+
if Application.get_env(:reencodarr, :env) != :test do
159+
Task.start(fn -> WebhookSync.reconcile_all() end)
160+
end
161+
end
162+
152163
# Tell Phoenix to update the endpoint configuration
153164
# whenever the application is updated.
154165
@impl true

lib/reencodarr/services.ex

Lines changed: 56 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,13 @@ defmodule Reencodarr.Services do
22
@moduledoc """
33
This module is responsible for communicating with external services.
44
"""
5+
require Logger
56
alias Reencodarr.DbWriter
67
alias Reencodarr.Repo
78
alias Reencodarr.Services.Config
89
alias Reencodarr.Services.Radarr
910
alias Reencodarr.Services.Sonarr
11+
alias Reencodarr.Services.WebhookSync
1012

1113
@doc """
1214
Returns the list of configs.
@@ -80,14 +82,21 @@ defmodule Reencodarr.Services do
8082
8183
"""
8284
def create_config(attrs \\ %{}) do
83-
DbWriter.run(
84-
fn ->
85-
%Config{}
86-
|> Config.changeset(attrs)
87-
|> Repo.insert()
88-
end,
89-
label: :service_config_create
90-
)
85+
case DbWriter.run(
86+
fn ->
87+
%Config{}
88+
|> Config.changeset(attrs)
89+
|> Repo.insert()
90+
end,
91+
label: :service_config_create
92+
) do
93+
{:ok, config} = result ->
94+
maybe_reconcile_managed_webhook(config)
95+
result
96+
97+
error ->
98+
error
99+
end
91100
end
92101

93102
@doc """
@@ -103,14 +112,21 @@ defmodule Reencodarr.Services do
103112
104113
"""
105114
def update_config(%Config{} = config, attrs) do
106-
DbWriter.run(
107-
fn ->
108-
config
109-
|> Config.changeset(attrs)
110-
|> Repo.update()
111-
end,
112-
label: :service_config_update
113-
)
115+
case DbWriter.run(
116+
fn ->
117+
config
118+
|> Config.changeset(attrs)
119+
|> Repo.update()
120+
end,
121+
label: :service_config_update
122+
) do
123+
{:ok, updated_config} = result ->
124+
maybe_reconcile_managed_webhook(updated_config)
125+
result
126+
127+
error ->
128+
error
129+
end
114130
end
115131

116132
@doc """
@@ -157,4 +173,28 @@ defmodule Reencodarr.Services do
157173

158174
@doc "Fetches all movie files for a given movie."
159175
def get_movie_files(movie_id), do: Radarr.get_movie_files(movie_id)
176+
177+
defp maybe_reconcile_managed_webhook(%Config{service_type: service_type})
178+
when service_type in [:sonarr, :radarr] do
179+
if Application.get_env(:reencodarr, :auto_reconcile_managed_webhooks, true) do
180+
case WebhookSync.reconcile_for_service(service_type) do
181+
:ok ->
182+
:ok
183+
184+
:skipped ->
185+
:ok
186+
187+
{:error, reason} ->
188+
Logger.warning(
189+
"Services: failed to reconcile #{service_type} webhook after config change: #{inspect(reason)}"
190+
)
191+
192+
:ok
193+
end
194+
else
195+
:ok
196+
end
197+
end
198+
199+
defp maybe_reconcile_managed_webhook(_config), do: :ok
160200
end

lib/reencodarr/services/radarr.ex

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,26 @@ defmodule Reencodarr.Services.Radarr do
3232
request(url: "/api/v3/movie?includeImages=false", method: :get)
3333
end
3434

35+
@spec list_notifications() :: {:ok, Req.Response.t()} | {:error, any()}
36+
def list_notifications do
37+
request(url: "/api/v3/notification", method: :get)
38+
end
39+
40+
@spec get_notification_schemas() :: {:ok, Req.Response.t()} | {:error, any()}
41+
def get_notification_schemas do
42+
request(url: "/api/v3/notification/schema", method: :get)
43+
end
44+
45+
@spec create_notification(map()) :: {:ok, Req.Response.t()} | {:error, any()}
46+
def create_notification(attrs) when is_map(attrs) do
47+
request(url: "/api/v3/notification", method: :post, json: attrs)
48+
end
49+
50+
@spec update_notification(integer(), map()) :: {:ok, Req.Response.t()} | {:error, any()}
51+
def update_notification(id, attrs) when is_integer(id) and id > 0 and is_map(attrs) do
52+
request(url: "/api/v3/notification/#{id}", method: :put, json: attrs)
53+
end
54+
3555
@spec get_movie_files(integer()) :: {:ok, Req.Response.t()} | {:error, any()}
3656
def get_movie_files(movie_id) do
3757
request(url: "/api/v3/moviefile?movieId=#{movie_id}", method: :get)

lib/reencodarr/services/sonarr.ex

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,26 @@ defmodule Reencodarr.Services.Sonarr do
3636
request(url: "/api/v3/series?includeSeasonImages=false", method: :get)
3737
end
3838

39+
@spec list_notifications() :: {:ok, Req.Response.t()} | {:error, any()}
40+
def list_notifications do
41+
request(url: "/api/v3/notification", method: :get)
42+
end
43+
44+
@spec get_notification_schemas() :: {:ok, Req.Response.t()} | {:error, any()}
45+
def get_notification_schemas do
46+
request(url: "/api/v3/notification/schema", method: :get)
47+
end
48+
49+
@spec create_notification(map()) :: {:ok, Req.Response.t()} | {:error, any()}
50+
def create_notification(attrs) when is_map(attrs) do
51+
request(url: "/api/v3/notification", method: :post, json: attrs)
52+
end
53+
54+
@spec update_notification(integer(), map()) :: {:ok, Req.Response.t()} | {:error, any()}
55+
def update_notification(id, attrs) when is_integer(id) and id > 0 and is_map(attrs) do
56+
request(url: "/api/v3/notification/#{id}", method: :put, json: attrs)
57+
end
58+
3959
@spec get_episode_files(integer()) :: {:ok, Req.Response.t()} | {:error, any()}
4060
def get_episode_files(series_id) do
4161
response = request(url: "/api/v3/episodefile?seriesId=#{series_id}", method: :get)

0 commit comments

Comments
 (0)