8000 Feature/sync raindrop by electronicbites · Pull Request #611 · podlove/radiator · GitHub
[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
Skip to content
8000

Feature/sync raindrop #611

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
May 21, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 81 additions & 3 deletions lib/radiator/accounts/raindrop.ex
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,55 @@ defmodule Radiator.Accounts.Raindrop do
iex> connect_show_with_raindrop(999, 23, 42)
{:error, "No Raindrop tokens found"}
"""
def connect_show_with_raindrop(user_id, show_id, collection_id, node_id \\ nil) do
def connect_show_with_raindrop(user_id, show_id, collection_id) do
case get_raindrop_tokens(user_id) do
nil ->
{:error, "No Raindrop tokens found"}

%{data: data} = service ->
updated_mappings = update_mappings(data.mappings, show_id, collection_id, node_id)
# Ensure mappings is a list
mappings = data.mappings || []
updated_mappings = update_mappings(mappings, show_id, collection_id, nil)

service
|> WebService.changeset(%{
data: %{
access_token: data.access_token,
refresh_token: data.refresh_token,
expires_at: data.expires_at,
mappings: updated_mappings
}
})
|> Repo.update()
end
end

@doc """
Saves an inbox node for a show for the Randrop-service.
Preserves the existing collection_id that was previously set for this show and user.

## Examples

iex> set_inbox_node_for_raindrop(1, 23, 42)
{:ok, %WebService{}}

iex> set_inbox_node_for_raindrop(999, 23, 42)
{:error, "No Raindrop tokens found"}
"""
def set_inbox_node_for_raindrop(user_id, show_id, node_id) do
case get_raindrop_tokens(user_id) do
nil ->
{:error, "No Raindrop tokens found"}

%{data: data} = service ->
# Ensure mappings is a list
mappings = data.mappings || []

# Find existing collection_id for this specific user and show
existing_mapping = Enum.find(mappings, fn mapping -> mapping.show_id == show_id end)
collection_id = if existing_mapping, do: existing_mapping.collection_id, else: nil

updated_mappings = update_mappings(mappings, show_id, collection_id, node_id)

service
|> WebService.changeset(%{
Expand All @@ -100,9 +142,45 @@ defmodule Radiator.Accounts.Raindrop do

# Filter out any existing mapping with the same show_id and convert to maps
defp update_mappings(mappings, show_id, collection_id, node_id) do
mappings
(mappings || [])
|> Enum.reject(fn mapping -> mapping.show_id == show_id end)
|> Enum.map(&Map.from_struct/1)
|> Kernel.++([%{show_id: show_id, node_id: node_id, collection_id: collection_id}])
end

@doc """
Finds the user_id associated with a given show_id through Raindrop mappings.
This is only a temporal hack, should be replaced because this might be ambigous in the future!!

## Examples

iex> find_user_id_by_show_id(42)
{:ok, 23}

iex> find_user_id_by_show_id(999)
{:error, :not_found}

"""
def find_user_id_by_show_id(show_id) do
service_name = WebService.raindrop_service_name()

query =
from(ws in "web_services",
where: ws.service_name == ^service_name,
join:
m in fragment(
"jsonb_array_elements(?->'mappings')",
ws.data
),
on: true,
where: fragment("(?->>'show_id')::int = ?", m, ^show_id),
where: fragment("?->'node_id' IS NULL", m),
select: ws.user_id
)

case Repo.one(query) do
nil -> {:error, :not_found}
user_id -> {:ok, user_id}
end
end
end
14 changes: 11 additions & 3 deletions lib/radiator/accounts/web_service/raindrop_service.ex
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,16 @@ defmodule Radiator.Accounts.WebService.RaindropService do
end

defp mapping_changeset(mapping, attrs) do
mapping
|> cast(attrs, [:show_id, :collection_id, :node_id])
|> validate_required([:show_id, :collection_id])
changeset =
mapping
|> cast(attrs, [:show_id, :collection_id, :node_id])
|> validate_required([:show_id])

# Make collection_id required only if node_id is not being set
if get_change(changeset, :node_id) do
changeset
else
validate_required(changeset, [:collection_id])
end
end
end
17 changes: 16 additions & 1 deletion lib/radiator/outline/node_change_listener.ex
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ defmodule Radiator.Outline.NodeChangeListener do
NodeMovedEvent
}

alias Radiator.Accounts.Raindrop
alias Radiator.Outline.Dispatch
alias Radiator.Resources.NodeChangedWorker

Expand Down Expand Up @@ -59,8 +60,22 @@ defmodule Radiator.Outline.NodeChangeListener do

defp process_system_nodes_if(%NodeInsertedEvent{
user_id: nil,
node: %{content: "raindrop", container_id: _container_id, uuid: _uuid}
node: %{content: "raindrop", container_id: container_id, uuid: raindrop_node_uuid}
}) do
show = Radiator.Podcast.get_show_with_inbox_id(container_id)

case Raindrop.find_user_id_by_show_id(show.id) do
{:error, :not_found} ->
Logger.error("User not found for show #{show.id}")

{:ok, user_id} ->
Raindrop.set_inbox_node_for_raindrop(
user_id,
show.id,
raindrop_node_uuid
)
end

:ok
end

Expand Down
19 changes: 19 additions & 0 deletions lib/radiator/podcast.ex
Original file line number Diff line number Diff line change
Expand Up @@ -351,6 +351,25 @@ defmodule Radiator.Podcast do
Show.changeset(show, attrs)
end

@doc """
Returns a show with the given inbox node container id.

## Examples

iex> get_show_with_inbox_id(container_id)
%Show{}

iex> get_show_with_inbox_id(container_id)
nil

"""
def get_show_with_inbox_id(container_id) do
from(e in Show,
where: [inbox_node_container_id: ^container_id]
)
|> Repo.one()
end

@doc """
A forced reload of preloaded associations.
Usefull when only the associations have changed and Show does need to be reloaded.
Expand Down
4 changes: 2 additions & 2 deletions lib/radiator/resources/node_changed_worker.ex
Original file line number Diff line number Diff line cha AE96 nge
Expand Up @@ -6,7 +6,7 @@ defmodule Radiator.Resources.NodeChangedWorker do
alias Radiator.EpisodeOutliner
alias Radiator.NodeAnalyzer
alias Radiator.Outline.NodeRepository
alias Radiator.Resources
alias Radiator.ResourcesRepository

def trigger_analyze(node_id) do
Radiator.Job.start_job(
Expand All @@ -30,7 +30,7 @@ defmodule Radiator.Resources.NodeChangedWorker do
|> Map.put(:episode_id, episode_id)
end)

_created_urls = Resources.rebuild_node_urls(node_id, url_attributes)
_created_urls = ResourcesRepository.rebuild_node_urls(node_id, url_attributes)
:ok
end
end
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
defmodule Radiator.Resources do
defmodule Radiator.ResourcesRepository do
@moduledoc """
The Web context. All web related stuff, handing URLs,
scraped Websites etc..
Expand Down
14 changes: 11 additions & 3 deletions priv/repo/seeds.exs
Original file line number Diff line number Diff line change
Expand Up @@ -122,16 +122,24 @@ inbox_id = show.inbox_node_container_id

{:ok, inbox11} =
NodeRepository.create_node(%{
"content" => "Inbox 1",
"content" => "https://metaebene.me",
"container_id" => inbox_id,
"parent_id" => inbox1.uuid,
"prev_id" => nil
})

{:ok, _inbox12} =
{:ok, inbox12} =
NodeRepository.create_node(%{
"content" => "Inbox 2",
"content" => "https://freakshow.fm",
"container_id" => inbox_id,
"parent_id" => inbox1.uuid,
"prev_id" => inbox11.uuid
})

{:ok, _inbox13} =
NodeRepository.create_node(%{
"content" => "https://logbuch-netzpolitik.de",
"container_id" => inbox_id,
"parent_id" => inbox1.uuid,
"prev_id" => inbox12.uuid
})
59 changes: 59 additions & 0 deletions test/radiator/accounts/web_service_test.exs
Original file line number Diff line number Diff line change
Expand Up @@ -132,4 +132,63 @@ defmodule Radiator.Accounts.WebServiceTest do
]
end
end

describe "set_inbox_node_for_raindrop/3" do
setup do
%{web_service: raindrop_service_fixture(), show: PodcastFixtures.show_fixture()}
end

test "sets a node_id for a show while preserving the collection_id", %{
web_service: web_service,
show: show
} do
# First connect a show with a collection
{:ok, _} = Raindrop.connect_show_with_raindrop(web_service.user_id, show.id, 42)

# Now set a node_id for this show
node_id = Ecto.UUID.generate()
{:ok, _} = Raindrop.set_inbox_node_for_raindrop(web_service.user_id, show.id, node_id)

# Get the updated service and check
service = Raindrop.get_raindrop_tokens(web_service.user_id)

mappings = Enum.map(service.data.mappings, &Map.from_struct/1)
assert length(mappings) == 1
mapping = List.first(mappings)
assert mapping.collection_id == 42
assert mapping.node_id == node_id
assert mapping.show_id == show.id
end

test "sets a node_id for a show without existing collection_id", %{
web_service: web_service,
show: show
} do
# Set a node_id directly without first creating a mapping
node_id = Ecto.UUID.generate()
{:ok, _} = Raindrop.set_inbox_node_for_raindrop(web_service.user_id, show.id, node_id)

# Get the updated service and check
service = Raindrop.get_raindrop_tokens(web_service.user_id)

mappings = Enum.map(service.data.mappings, &Map.from_struct/1)
assert length(mappings) == 1
mapping = List.first(mappings)
assert mapping.collection_id == nil
assert mapping.node_id == node_id
assert mapping.show_id == show.id
end

test "returns an error when no raindrop tokens are found", %{
show: show
} do
# Use a non-existent user ID
non_existent_user_id = 999_999
node_id = Ecto.UUID.generate()

result = Raindrop.set_inbox_node_for_raindrop(non_existent_user_id, show.id, node_id)

assert result == {:error, "No Raindrop tokens found"}
end
end
end
Loading
Loading
0