-
Notifications
You must be signed in to change notification settings - Fork 14
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #588 from podlove/feature/polymorphic-services
add userservices model
- Loading branch information
Showing
14 changed files
with
478 additions
and
171 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
defmodule Radiator.Accounts.RaindropClient do | ||
@moduledoc """ | ||
Client for Raindrop API | ||
""" | ||
require Logger | ||
|
||
alias Radiator.Accounts | ||
|
||
def config, do: Application.fetch_env!(:radiator, :raindrop) | ||
|
||
def redirect_uri_encoded(user_id) do | ||
user_id | ||
|> redirect_uri() | ||
|> URI.encode() | ||
end | ||
|
||
def redirect_uri(user_id) do | ||
config()[:redirect_url] | ||
|> URI.parse() | ||
|> URI.append_query("user_id=#{user_id}") | ||
|> URI.to_string() | ||
end | ||
|
||
def redirect_uri do | ||
config()[:redirect_url] | ||
|> URI.parse() | ||
|> URI.to_string() | ||
end | ||
|
||
@doc """ | ||
Check if the user has access to Raindrop API | ||
""" | ||
def access_enabled?(user_id) do | ||
not_enabled = | ||
user_id | ||
|> Accounts.get_raindrop_tokens() | ||
|> is_nil() | ||
|
||
!not_enabled | ||
end | ||
|
||
@doc """ | ||
Get all collections for a user | ||
""" | ||
def get_collections(user_id) do | ||
service = | ||
user_id | ||
|> Accounts.get_raindrop_tokens() | ||
|> refresh_token_if() | ||
|
||
if is_nil(service) do | ||
{:error, :unauthorized} | ||
else | ||
[ | ||
method: :get, | ||
url: "https://api.raindrop.io/rest/v1/collections", | ||
headers: [ | ||
{"Authorization", "Bearer #{service.data.access_token}"} | ||
] | ||
] | ||
|> Req.request() | ||
|> parse_collection_response() | ||
end | ||
end | ||
|
||
@doc """ | ||
first time fetching access token and storing it as webservice entry | ||
""" | ||
def init_and_store_access_token(user_id, code) do | ||
{:ok, response} = | ||
[ | ||
method: :post, | ||
url: "https://raindrop.io/oauth/access_token", | ||
json: %{ | ||
client_id: config()[:client_id], | ||
client_secret: config()[:client_secret], | ||
grant_type: "authorization_code", | ||
code: code, | ||
redirect_uri: redirect_uri() | ||
} | ||
] | ||
|> Keyword.merge(config()[:options]) | ||
|> Req.request() | ||
|
||
parse_access_token_response(response, user_id) | ||
end | ||
|
||
defp refresh_token_if(service) do | ||
if DateTime.before?(service.data.expires_at, DateTime.utc_now()) do | ||
{:ok, response} = | ||
[ | ||
method: :post, | ||
url: "https://raindrop.io/oauth/access_token", | ||
headers: [ | ||
{"Content-Type", "application/json"} | ||
], | ||
json: %{ | ||
client_id: config()[:client_id], | ||
client_secret: config()[:client_secret], | ||
grant_type: "refresh_token", | ||
refresh_token: service.data.refresh_token | ||
} | ||
] | ||
|> Req.request() | ||
|
||
parse_access_token_response(response, service.user_id) | ||
else | ||
service | ||
end | ||
end | ||
|
||
defp parse_access_token_response( | ||
%Req.Response{ | ||
body: %{ | ||
"access_token" => access_token, | ||
"refresh_token" => refresh_token, | ||
"expires_in" => expires_in | ||
} | ||
}, | ||
user_id | ||
) do | ||
expires_at = | ||
DateTime.now!("Etc/UTC") | ||
|> DateTime.shift(second: expires_in) | ||
|> DateTime.truncate(:second) | ||
|
||
Accounts.update_raindrop_tokens( | ||
user_id, | ||
access_token, | ||
refresh_token, | ||
expires_at | ||
) | ||
end | ||
|
||
defp parse_access_token_response(response, _user_id) do | ||
Logger.error("Error fetching access token: #{inspect(response)}") | ||
{:error, :unauthorized} | ||
end | ||
|
||
defp parse_collection_response({:ok, %Req.Response{status: 401}}) do | ||
{:error, :unauthorized} | ||
end | ||
|
||
defp parse_collection_response({:ok, %Req.Response{body: body}}) do | ||
body | ||
|> Map.get("items") | ||
|> Enum.map(&Map.take(&1, ["_id", "title"])) | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
defmodule Radiator.Accounts.WebService do | ||
@moduledoc """ | ||
Model for storing all kinds of information about a user's service. | ||
First implementation is for Raindrop.io | ||
In the future we may have support for other services and https://hexdocs.pm/polymorphic_embed/ might be a solution | ||
""" | ||
use Ecto.Schema | ||
import Ecto.Changeset | ||
|
||
alias Radiator.Accounts.User | ||
|
||
@raindrop_service_name "raindrop" | ||
|
||
schema "web_services" do | ||
field :service_name, :string | ||
|
||
embeds_one :data, RaindropService, on_replace: :delete, primary_key: false do | ||
field :access_token, :string, redact: true | ||
field :refresh_token, :string, redact: true | ||
field :expires_at, :utc_datetime | ||
# Show ID => Raindrop Collection ID | ||
field :collection_mappings, :map, default: %{} | ||
end | ||
|
||
belongs_to :user, User | ||
|
||
timestamps(type: :utc_datetime) | ||
end | ||
|
||
@doc false | ||
def changeset(service, attrs) do | ||
service | ||
|> cast(attrs, [:service_name, :user_id]) | ||
|> cast_embed(:data, required: true, with: &raindrop_changeset/2) | ||
|> validate_required([:service_name, :data]) | ||
end | ||
|
||
def raindrop_changeset(service, attrs \\ %{}) do | ||
service | ||
|> cast(attrs, [:access_token, :refresh_token, :expires_at, :collection_mappings]) | ||
|> validate_required([:access_token, :refresh_token, :expires_at]) | ||
end | ||
|
||
def raindrop_service_name, do: @raindrop_service_name | ||
end |
This file was deleted.
Oops, something went wrong.
Oops, something went wrong.