mirror of
https://github.com/mgerb/classic-wow-forums
synced 2026-01-10 09:02:50 +00:00
server - adjust rate limiter - reply/thread mod update
This commit is contained in:
19
README.md
19
README.md
@@ -96,8 +96,27 @@ local all all trust
|
|||||||
systemctl restart postgresql
|
systemctl restart postgresql
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Postgres in docker container
|
||||||
|
```
|
||||||
|
docker run --name postgres1 -p 5432:5432 -e POSTGRES_PASSWORD=postgres -d postgres
|
||||||
|
```
|
||||||
|
|
||||||
# Issues encountered
|
# Issues encountered
|
||||||
|
|
||||||
- Building the client files fails with not enough ram
|
- 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
|
- 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
|
- 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ defmodule MyApp.Data.Reply do
|
|||||||
field :user_id, :integer # references :user
|
field :user_id, :integer # references :user
|
||||||
field :thread_id, :integer # references :thread
|
field :thread_id, :integer # references :thread
|
||||||
field :content, :string
|
field :content, :string
|
||||||
field :edited, :boolean, default: false
|
|
||||||
field :quote_id, :integer
|
field :quote_id, :integer
|
||||||
|
field :edited, :boolean, default: false
|
||||||
field :hidden, :boolean, default: false
|
field :hidden, :boolean, default: false
|
||||||
has_one :user, Data.User, foreign_key: :id, references: :user_id
|
has_one :user, Data.User, foreign_key: :id, references: :user_id
|
||||||
timestamps(type: :utc_datetime)
|
timestamps(type: :utc_datetime)
|
||||||
@@ -35,11 +35,20 @@ defmodule MyApp.Data.Reply do
|
|||||||
|
|
||||||
@spec insert(map) :: {:ok, map} | {:error, map}
|
@spec insert(map) :: {:ok, map} | {:error, map}
|
||||||
def insert(params) do
|
def insert(params) do
|
||||||
{:ok, data} = insert_changeset(%Data.Reply{}, params)
|
{:ok, data} = Repo.transaction(fn ->
|
||||||
|> Repo.insert
|
thread = Repo.get_by(Data.Thread, %{ id: Map.get(params, "thread_id")})
|
||||||
|> Data.Util.process_insert_or_update
|
|
||||||
|> update_thread_new_reply
|
if !thread.locked do
|
||||||
{:ok, Map.drop(data, [:user])} # drop user because we can't encode it if it's not preloaded
|
{: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
|
end
|
||||||
|
|
||||||
defp update_thread_new_reply({:error, error}), do: {:error, error}
|
defp update_thread_new_reply({:error, error}), do: {:error, error}
|
||||||
@@ -75,4 +84,15 @@ defmodule MyApp.Data.Reply do
|
|||||||
end
|
end
|
||||||
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
|
end
|
||||||
|
|||||||
@@ -12,11 +12,10 @@ defmodule MyApp.Data.Thread do
|
|||||||
field :view_count, :integer, default: 0
|
field :view_count, :integer, default: 0
|
||||||
field :user_id, :integer # references :user
|
field :user_id, :integer # references :user
|
||||||
field :last_reply_id, :integer
|
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 :reply_count, :integer, default: 0
|
||||||
field :hidden, :boolean, default: false
|
field :hidden, :boolean, default: false
|
||||||
|
field :locked, :boolean, default: false
|
||||||
|
field :sticky, :boolean, default: false
|
||||||
has_many :replies, Data.Reply
|
has_many :replies, Data.Reply
|
||||||
has_one :user, Data.User, foreign_key: :id, references: :user_id
|
has_one :user, Data.User, foreign_key: :id, references: :user_id
|
||||||
has_one :last_reply, Data.User, foreign_key: :id, references: :last_reply_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)
|
|> foreign_key_constraint(:user_id)
|
||||||
end
|
end
|
||||||
|
|
||||||
# TODO: allow mods to set sticky/locked on threads
|
@spec mod_update(map) :: {:ok, any}
|
||||||
defp mod_update_changeset(thread, params \\ %{}) do
|
def mod_update(params) do
|
||||||
thread
|
Repo.transaction(fn ->
|
||||||
|> cast(params, [:sticky, :locked])
|
reply = Repo.get_by(Data.Thread, %{ id: Map.get(params, "id")})
|
||||||
|
|
||||||
|
reply
|
||||||
|
|> cast(params, [:hidden, :sticky, :locked])
|
||||||
|
|> Repo.update
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
def get(thread_id) do
|
def get(thread_id) do
|
||||||
@@ -56,7 +60,6 @@ defmodule MyApp.Data.Thread do
|
|||||||
:sticky,
|
:sticky,
|
||||||
:locked,
|
:locked,
|
||||||
:last_reply_id,
|
:last_reply_id,
|
||||||
:edited,
|
|
||||||
:category_id,
|
:category_id,
|
||||||
:title,
|
:title,
|
||||||
:view_count,
|
:view_count,
|
||||||
|
|||||||
@@ -4,14 +4,19 @@ defmodule MyApp.RateLimiter do
|
|||||||
def new_reply_key, do: 1
|
def new_reply_key, do: 1
|
||||||
def new_thread_key, do: 2
|
def new_thread_key, do: 2
|
||||||
|
|
||||||
@spec limit(String.t, integer, integer) :: {:ok, String.t} | {:error, String.t}
|
@spec set_limit(String.t, integer, integer) :: {:ok, String.t}
|
||||||
def limit(end_point, user_id, seconds) do
|
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}"
|
key = "rl#{end_point}:#{user_id}"
|
||||||
case Cachex.get(:myapp, key) do
|
case Cachex.get(:myapp, key) do
|
||||||
{:missing, _} ->
|
|
||||||
Cachex.set(:myapp, key, true, ttl: :timer.seconds(seconds))
|
|
||||||
{:ok, "ok"}
|
|
||||||
{:ok, _} -> {:error, "limit reached"}
|
{:ok, _} -> {:error, "limit reached"}
|
||||||
|
{:missing, _} -> {:ok, "ok"}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|||||||
@@ -10,10 +10,18 @@ defmodule MyAppWeb.ReplyController do
|
|||||||
|> MyApp.Guardian.Plug.current_claims
|
|> MyApp.Guardian.Plug.current_claims
|
||||||
|> Map.get("id")
|
|> Map.get("id")
|
||||||
|
|
||||||
{output, status} = case RateLimiter.limit(RateLimiter.new_reply_key, user_id, 60) do
|
{output, status} = case RateLimiter.check_limit(RateLimiter.new_reply_key, user_id) do
|
||||||
{:ok, _} -> params
|
{:ok, _} ->
|
||||||
|
{ok, data} = params
|
||||||
|> Map.put("user_id", user_id)
|
|> Map.put("user_id", user_id)
|
||||||
|> Data.Reply.insert
|
|> 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
|
|> Response.put_resp
|
||||||
|
|
||||||
{:error, error} -> {error, 429}
|
{:error, error} -> {error, 429}
|
||||||
@@ -40,4 +48,12 @@ defmodule MyAppWeb.ReplyController do
|
|||||||
|> Response.json(output)
|
|> Response.json(output)
|
||||||
end
|
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
|
end
|
||||||
|
|||||||
@@ -11,11 +11,21 @@ defmodule MyAppWeb.ThreadController do
|
|||||||
|> Map.get("id")
|
|> Map.get("id")
|
||||||
|
|
||||||
# every 5 minutes user can submit new thread
|
# every 5 minutes user can submit new thread
|
||||||
{output, status} = case RateLimiter.limit(RateLimiter.new_thread_key, user_id, 300) do
|
{output, status} = case RateLimiter.check_limit(RateLimiter.new_thread_key, user_id) do
|
||||||
{:ok, _} -> params
|
{:ok, _} ->
|
||||||
|
|
||||||
|
{ok, data} = params
|
||||||
|> Map.put("user_id", user_id)
|
|> Map.put("user_id", user_id)
|
||||||
|> Data.Thread.insert
|
|> 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
|
|> Response.put_resp
|
||||||
|
|
||||||
{:error, error} -> {error, 429}
|
{:error, error} -> {error, 429}
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -71,4 +81,12 @@ defmodule MyAppWeb.ThreadController do
|
|||||||
|> Response.json(output)
|
|> Response.json(output)
|
||||||
end
|
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
|
end
|
||||||
|
|||||||
@@ -39,6 +39,9 @@ defmodule MyAppWeb.Router do
|
|||||||
pipe_through [:user_auth]
|
pipe_through [:user_auth]
|
||||||
post "/", ThreadController, :insert
|
post "/", ThreadController, :insert
|
||||||
put "/", ThreadController, :update
|
put "/", ThreadController, :update
|
||||||
|
|
||||||
|
pipe_through [:mod_auth]
|
||||||
|
put "/mod", ThreadController, :mod_update
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/reply" do
|
scope "/reply" do
|
||||||
@@ -46,6 +49,9 @@ defmodule MyAppWeb.Router do
|
|||||||
pipe_through [:user_auth]
|
pipe_through [:user_auth]
|
||||||
post "/", ReplyController, :insert
|
post "/", ReplyController, :insert
|
||||||
put "/", ReplyController, :update
|
put "/", ReplyController, :update
|
||||||
|
|
||||||
|
pipe_through [:mod_auth]
|
||||||
|
put "/mod", ReplyController, :mod_update
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/category" do
|
scope "/category" do
|
||||||
|
|||||||
10
priv/repo/migrations/20180126012522_thread_remove_edited.exs
Normal file
10
priv/repo/migrations/20180126012522_thread_remove_edited.exs
Normal file
@@ -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
|
||||||
@@ -195,6 +195,7 @@ Enum.each(Category.get_seed(), fn(cat) ->
|
|||||||
end)
|
end)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
# TODO: Fix this for testing
|
||||||
# insert admin user
|
# insert admin user
|
||||||
accounts = Application.get_env(:myapp, :admin_accounts)
|
accounts = Application.get_env(:myapp, :admin_accounts)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user