diff --git a/README.md b/README.md index 48c75bd..21b1fbc 100644 --- a/README.md +++ b/README.md @@ -96,8 +96,27 @@ local all all trust systemctl restart postgresql ``` +## Postgres in docker container +``` +docker run --name postgres1 -p 5432:5432 -e POSTGRES_PASSWORD=postgres -d postgres +``` + # Issues encountered - Building the client files fails with not enough ram - It runs out of ram on a Centos vps so I needed to add more swap space - https://www.digitalocean.com/community/tutorials/how-to-add-swap-on-centos-7 + +# Battlenet API +Battlenet required https for a redirect for authentication. I use caddy for https proxy during development. + +Caddyfile +``` +https://localhost { + tls self_signed + proxy / http://localhost:8080 { + transparent + websocket + } +} +``` diff --git a/lib/myapp/data/reply.ex b/lib/myapp/data/reply.ex index cec6229..bc4ec36 100644 --- a/lib/myapp/data/reply.ex +++ b/lib/myapp/data/reply.ex @@ -10,8 +10,8 @@ defmodule MyApp.Data.Reply do field :user_id, :integer # references :user field :thread_id, :integer # references :thread field :content, :string - field :edited, :boolean, default: false field :quote_id, :integer + field :edited, :boolean, default: false field :hidden, :boolean, default: false has_one :user, Data.User, foreign_key: :id, references: :user_id timestamps(type: :utc_datetime) @@ -35,11 +35,20 @@ defmodule MyApp.Data.Reply do @spec insert(map) :: {:ok, map} | {:error, map} def insert(params) do - {:ok, data} = insert_changeset(%Data.Reply{}, params) - |> Repo.insert - |> Data.Util.process_insert_or_update - |> update_thread_new_reply - {:ok, Map.drop(data, [:user])} # drop user because we can't encode it if it's not preloaded + {:ok, data} = Repo.transaction(fn -> + thread = Repo.get_by(Data.Thread, %{ id: Map.get(params, "thread_id")}) + + if !thread.locked do + {:ok, data} = insert_changeset(%Data.Reply{}, params) + |> Repo.insert + |> Data.Util.process_insert_or_update + |> update_thread_new_reply + {:ok, Map.drop(data, [:user])} # drop user because we can't encode it if it's not preloaded + else + {:error, "thread locked"} + end + end) + data end defp update_thread_new_reply({:error, error}), do: {:error, error} @@ -75,4 +84,15 @@ defmodule MyApp.Data.Reply do end end + @spec mod_update(map) :: {:ok, any} + def mod_update(params) do + Repo.transaction(fn -> + reply = Repo.get_by(Data.Reply, %{ id: Map.get(params, "id")}) + + reply + |> cast(params, [:hidden]) + |> Repo.update + end) + end + end diff --git a/lib/myapp/data/thread.ex b/lib/myapp/data/thread.ex index bca401d..e2cb302 100644 --- a/lib/myapp/data/thread.ex +++ b/lib/myapp/data/thread.ex @@ -12,11 +12,10 @@ defmodule MyApp.Data.Thread do field :view_count, :integer, default: 0 field :user_id, :integer # references :user field :last_reply_id, :integer - field :sticky, :boolean, default: false - field :locked, :boolean, default: false - field :edited, :boolean, default: false field :reply_count, :integer, default: 0 field :hidden, :boolean, default: false + field :locked, :boolean, default: false + field :sticky, :boolean, default: false has_many :replies, Data.Reply has_one :user, Data.User, foreign_key: :id, references: :user_id has_one :last_reply, Data.User, foreign_key: :id, references: :last_reply_id @@ -31,10 +30,15 @@ defmodule MyApp.Data.Thread do |> foreign_key_constraint(:user_id) end - # TODO: allow mods to set sticky/locked on threads - defp mod_update_changeset(thread, params \\ %{}) do - thread - |> cast(params, [:sticky, :locked]) + @spec mod_update(map) :: {:ok, any} + def mod_update(params) do + Repo.transaction(fn -> + reply = Repo.get_by(Data.Thread, %{ id: Map.get(params, "id")}) + + reply + |> cast(params, [:hidden, :sticky, :locked]) + |> Repo.update + end) end def get(thread_id) do @@ -56,7 +60,6 @@ defmodule MyApp.Data.Thread do :sticky, :locked, :last_reply_id, - :edited, :category_id, :title, :view_count, diff --git a/lib/myapp/rate_limiter.ex b/lib/myapp/rate_limiter.ex index c97623b..e33d611 100644 --- a/lib/myapp/rate_limiter.ex +++ b/lib/myapp/rate_limiter.ex @@ -4,14 +4,19 @@ defmodule MyApp.RateLimiter do def new_reply_key, do: 1 def new_thread_key, do: 2 - @spec limit(String.t, integer, integer) :: {:ok, String.t} | {:error, String.t} - def limit(end_point, user_id, seconds) do + @spec set_limit(String.t, integer, integer) :: {:ok, String.t} + def set_limit(end_point, user_id, seconds) do + key = "rl#{end_point}:#{user_id}" + Cachex.set(:myapp, key, true, ttl: :timer.seconds(seconds)) + {:ok, "ok"} + end + + @spec check_limit(String.t, integer) :: {:ok, String.t} | {:error, String.t} + def check_limit(end_point, user_id) do key = "rl#{end_point}:#{user_id}" case Cachex.get(:myapp, key) do - {:missing, _} -> - Cachex.set(:myapp, key, true, ttl: :timer.seconds(seconds)) - {:ok, "ok"} {:ok, _} -> {:error, "limit reached"} + {:missing, _} -> {:ok, "ok"} end end diff --git a/lib/myapp_web/controllers/reply_controller.ex b/lib/myapp_web/controllers/reply_controller.ex index 86b1e7b..4dd775f 100644 --- a/lib/myapp_web/controllers/reply_controller.ex +++ b/lib/myapp_web/controllers/reply_controller.ex @@ -10,10 +10,18 @@ defmodule MyAppWeb.ReplyController do |> MyApp.Guardian.Plug.current_claims |> Map.get("id") - {output, status} = case RateLimiter.limit(RateLimiter.new_reply_key, user_id, 60) do - {:ok, _} -> params + {output, status} = case RateLimiter.check_limit(RateLimiter.new_reply_key, user_id) do + {:ok, _} -> + {ok, data} = params |> Map.put("user_id", user_id) |> Data.Reply.insert + + if ok == :ok do + # apply rate limiter only after submitting new reply + RateLimiter.set_limit(RateLimiter.new_reply_key, user_id, 60) + end + + {ok, data} |> Response.put_resp {:error, error} -> {error, 429} @@ -39,5 +47,13 @@ defmodule MyAppWeb.ReplyController do |> put_status(status) |> Response.json(output) end + + @spec mod_update(map, map) :: any + def mod_update(conn, params) do + {:ok, _} = Data.Reply.mod_update(params) + conn + |> put_status(200) + |> Response.json("ok") + end end diff --git a/lib/myapp_web/controllers/thread_controller.ex b/lib/myapp_web/controllers/thread_controller.ex index 076bd22..f723542 100644 --- a/lib/myapp_web/controllers/thread_controller.ex +++ b/lib/myapp_web/controllers/thread_controller.ex @@ -11,11 +11,21 @@ defmodule MyAppWeb.ThreadController do |> Map.get("id") # every 5 minutes user can submit new thread - {output, status} = case RateLimiter.limit(RateLimiter.new_thread_key, user_id, 300) do - {:ok, _} -> params + {output, status} = case RateLimiter.check_limit(RateLimiter.new_thread_key, user_id) do + {:ok, _} -> + + {ok, data} = params |> Map.put("user_id", user_id) |> Data.Thread.insert + + if ok == :ok do + # apply rate limiter only after submitting new post + RateLimiter.set_limit(RateLimiter.new_thread_key, user_id, 300) + end + + {ok, data} |> Response.put_resp + {:error, error} -> {error, 429} end @@ -71,4 +81,12 @@ defmodule MyAppWeb.ThreadController do |> Response.json(output) end + @spec mod_update(map, map) :: any + def mod_update(conn, params) do + {:ok, _} = Data.Thread.mod_update(params) + conn + |> put_status(200) + |> Response.json("ok") + end + end diff --git a/lib/myapp_web/router.ex b/lib/myapp_web/router.ex index a964c70..11e964e 100644 --- a/lib/myapp_web/router.ex +++ b/lib/myapp_web/router.ex @@ -39,6 +39,9 @@ defmodule MyAppWeb.Router do pipe_through [:user_auth] post "/", ThreadController, :insert put "/", ThreadController, :update + + pipe_through [:mod_auth] + put "/mod", ThreadController, :mod_update end scope "/reply" do @@ -46,6 +49,9 @@ defmodule MyAppWeb.Router do pipe_through [:user_auth] post "/", ReplyController, :insert put "/", ReplyController, :update + + pipe_through [:mod_auth] + put "/mod", ReplyController, :mod_update end scope "/category" do diff --git a/priv/repo/migrations/20180126012522_thread_remove_edited.exs b/priv/repo/migrations/20180126012522_thread_remove_edited.exs new file mode 100644 index 0000000..979717c --- /dev/null +++ b/priv/repo/migrations/20180126012522_thread_remove_edited.exs @@ -0,0 +1,10 @@ +defmodule MyApp.Repo.Migrations.ThreadRemoveEdited do + use Ecto.Migration + + def change do + alter table(:thread) do + remove :edited + end + end + +end diff --git a/priv/repo/seeds.exs b/priv/repo/seeds.exs index bb6a872..d8aef3d 100644 --- a/priv/repo/seeds.exs +++ b/priv/repo/seeds.exs @@ -195,6 +195,7 @@ Enum.each(Category.get_seed(), fn(cat) -> end) end) +# TODO: Fix this for testing # insert admin user accounts = Application.get_env(:myapp, :admin_accounts)