Skip to content

Commit

Permalink
Add push to Trello board
Browse files Browse the repository at this point in the history
  • Loading branch information
sixFingers committed Aug 22, 2016
1 parent 085ac5f commit e3f3ea8
Show file tree
Hide file tree
Showing 8 changed files with 192 additions and 16 deletions.
11 changes: 6 additions & 5 deletions config/config.exs
Original file line number Diff line number Diff line change
Expand Up @@ -36,16 +36,17 @@ config :phamello, Phamello.Picture,
storage_path: System.get_env("IMAGE_STORAGE_FOLDER"),
max_file_size: 5_000_000

config :phamello, :s3_client,
aws_access_key_id: System.get_env("AWS_ACCESS_KEY_ID"),
aws_secret_access_key: System.get_env("AWS_SECRET_ACCESS_KEY"),
bucket_name: System.get_env("IMAGE_STORAGE_BUCKET")

config :phamello, Phamello.S3Client,
aws_access_key_id: System.get_env("AWS_ACCESS_KEY_ID"),
aws_secret_access_key: System.get_env("AWS_SECRET_ACCESS_KEY"),
bucket_name: System.get_env("IMAGE_STORAGE_BUCKET")

config :phamello, Phamello.TrelloClient,
api_key: System.get_env("TRELLO_API_KEY"),
api_token: System.get_env("TRELLO_API_TOKEN"),
board_name: System.get_env("TRELLO_BOARD_NAME"),
list_name: System.get_env("TRELLO_LIST_NAME")

# Import environment specific config. This must remain at the bottom
# of this file so it overrides the configuration defined above.
import_config "#{Mix.env}.exs"
110 changes: 110 additions & 0 deletions lib/phamello/clients/trello_client.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
defmodule Phamello.TrelloClient do
alias Phamello.Picture

@api_url "https://api.trello.com"
@boards_path "/1/members/me"
@cards_path "/1/cards"

def create_card(%Picture{} = picture) do
case get_list_id(config[:board_name], config[:list_name]) do
{:ok, list_id} -> do_create_card(list_id, picture)
{:error, error} -> {:error, error}
end
end

def do_create_card(list_id, picture) do
@cards_path
|> full_url(card_params(list_id, picture))
|> HTTPoison.post!("")
|> parse_response
end

def get_list_id(board, list) do
case fetch_boards do
{:ok, boards} -> {:ok, do_get_list_id(boards, board, list)}
{:error, error} -> {:error, error}
end
end

def do_get_list_id(boards, board, list) do
boards
|> map_boards
|> select_board(board)
|> select_board_list(list)
end

defp select_board(boards, board) do
case Map.get(boards, board, nil) do
nil -> raise "Board not found"
board -> board
end
end

defp select_board_list(board, list) do
case Map.get(board, list, nil) do
nil -> raise "List not found"
list -> list
end
end

defp fetch_boards do
@boards_path
|> full_url(boards_params)
|> HTTPoison.get!
|> parse_response
end

defp map_boards(%{"boards" => boards}) do
boards
|> Enum.reduce(%{}, fn(%{"name" => name, "lists" => lists}, acc) ->
Map.put(acc, name, map_lists(lists))
end)
end

defp map_lists(lists) do
lists
|> Enum.reduce(%{}, fn(%{"id" => id, "name" => name}, acc) ->
Map.put(acc, name, id)
end)
end

defp parse_response(%HTTPoison.Response{status_code: 200, body: body}), do:
{:ok, Poison.decode!(body)}

defp parse_response(%HTTPoison.Response{status_code: _, body: body}), do:
{:error, body}

defp full_url(path, params) do
query = URI.encode_query(params)

@api_url
|> URI.merge(path)
|> URI.merge("?#{query}")
|> URI.to_string
end

defp config, do: Application.get_env(:phamello, __MODULE__)

defp auth_params do
[key: config[:api_key], token: config[:api_token]]
|> Enum.into(%{})
end

defp boards_params do
auth_params
|> Map.merge(%{
"boards" => "all",
"board_lists" => "all"
})
end

defp card_params(list_id, picture) do
auth_params
|> Map.merge(%{
"idList" => list_id,
"name" => picture.name,
"due" => picture.updated_at,
"desc" => "#{picture.user.username}\n#{picture.remote_url}"
})
end
end
18 changes: 18 additions & 0 deletions lib/phamello/picture/trello_tasks.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
defmodule Phamello.TrelloTasks do
alias Phamello.{Picture, TrelloClient}

def push_to_board(pid, %Picture{} = picture) do
case TrelloClient.create_card(picture) do
{:ok, %{"url" => url}} -> confirm_trello_push(pid, picture.id, url)
{:error, error} -> bail_trello_push(pid, picture.id, error)
end
end

defp confirm_trello_push(pid, picture_id, url) do
GenServer.cast(pid, {:trello_notify_complete, picture_id, url})
end

defp bail_trello_push(pid, picture_id, error) do
GenServer.cast(pid, {:trello_notify_error, picture_id, error})
end
end
42 changes: 36 additions & 6 deletions lib/phamello/picture/worker.ex
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
defmodule Phamello.PictureWorker do
use GenServer
alias Phamello.{Repo, Picture, S3Client, S3Tasks}
alias Phamello.{Repo, Picture, S3Client, S3Tasks, TrelloTasks}
require Logger

@s3_upload_error_message "Error uploading to S3 with image id:"
@trello_notify_error "error pushing to Trello image with id:"

# Client

Expand Down Expand Up @@ -35,25 +36,54 @@ defmodule Phamello.PictureWorker do
end

def handle_cast({:s3_upload_complete, picture_id, remote_url}, state) do
picture = Repo.get!(Picture, picture_id)
picture = Picture |> Repo.get!(picture_id) |> Repo.preload(:user)
changeset = Picture.update_changeset(picture, %{"remote_url" => remote_url})

case Repo.update(changeset) do
{:ok, _picture} -> :ok
{:ok, _picture} ->
GenServer.cast(__MODULE__, {:trello_notify_start, picture})
{:error, _changeset} ->
Logger.error "#{@s3_upload_error_message} #{picture_id}"
end

{:noreply, state}
end

def handle_cast({:s3_upload_error, _}, state) do
IO.puts("Error uploading to S3")
def handle_cast({:s3_upload_error, picture_id}, state) do
Logger.error "Error uploading to S3 image with id: #{picture_id}"
{:noreply, state}
end

def handle_cast({:trello_notify_start, %Picture{} = picture}, state) do
Task.Supervisor.start_child(
PictureSupervisor,
TrelloTasks,
:push_to_board,
[__MODULE__, picture]
)

{:noreply, state}
end

def handle_cast({:trello_notify_complete, picture_id, card_url}, state) do
picture = Picture |> Repo.get!(picture_id) |> Repo.preload(:user)
changeset = Picture.update_changeset(picture, %{"trello_url" => card_url})
{status, data} = Repo.update(changeset)

if status == :error do
Logger.error "#{@s3_upload_error_message} #{picture_id}"
end

{:noreply, state}
end

def handle_cast({:trello_notify_error, picture_id, error}, state) do
Logger.error "#{error} #{@trello_notify_error} #{picture_id}"
{:noreply, state}
end

def handle_info(msg, state) do
IO.puts("Info: #{msg}")
Logger.info msg
{:noreply, state}
end
end
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,7 @@ defmodule Phamello.Repo.Migrations.AddPictureRemoteUrl do
alter table(:pictures) do
add :remote_url, :string
end

create index(:pictures, [:remote_url])
end
end
11 changes: 11 additions & 0 deletions priv/repo/migrations/20160822230741_add_picture_trello_url.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
defmodule Phamello.Repo.Migrations.AddPictureTrelloUrl do
use Ecto.Migration

def change do
alter table(:pictures) do
add :trello_url, :string
end

create index(:pictures, [:trello_url])
end
end
10 changes: 7 additions & 3 deletions test/lib/picture_worker_test.exs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
defmodule Phamello.PictureWorkerTest do
use Phamello.ConnCase
import Phamello.Factory
alias Phamello.{Repo, User, Picture, StorageHelper}
alias Phamello.{Repo, User, Picture, StorageHelper, PictureWorker, TrelloTasks}
import Mock

@fake_remote_url "this_is_a_remote_url"

Expand All @@ -16,14 +17,17 @@ defmodule Phamello.PictureWorkerTest do
}}
end

test "creation changeset with invalid attributes", %{user: user, picture_map: picture_map} do
test_with_mock "creation changeset with invalid attributes",
%{user: user, picture_map: picture_map},
TrelloTasks, [], [push_to_board: fn(_, __) -> :ok end] do

picture = build_assoc(user, :pictures)
|> Picture.create_changeset(picture_map)
|> Repo.insert!

{:ok, state} = Phamello.PictureWorker.init([])

Phamello.PictureWorker.handle_cast({:s3_upload_complete, picture.id, @fake_remote_url}, state)
PictureWorker.handle_cast({:s3_upload_complete, picture.id, @fake_remote_url}, state)

picture = Repo.get!(Picture, picture.id)
assert picture.remote_url == @fake_remote_url
Expand Down
4 changes: 2 additions & 2 deletions web/models/picture.ex
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ defmodule Phamello.Picture do
field :description, :string
field :local_url, :string
field :remote_url, :string
field :trello_url, :string
field :image, :any, virtual: true

belongs_to :user, Phamello.User
Expand All @@ -33,8 +34,7 @@ defmodule Phamello.Picture do

def update_changeset(struct, params \\ %{}) do
struct
|> cast(params, [:remote_url])
|> validate_required([:remote_url])
|> cast(params, [:remote_url, :trello_url])
end

defp validate_image(changeset) do
Expand Down

0 comments on commit e3f3ea8

Please sign in to comment.