mirror of
https://github.com/mgerb/classic-wow-forums
synced 2026-01-09 08:42:47 +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
|
||||
```
|
||||
|
||||
## 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
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
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)
|
||||
|
||||
# TODO: Fix this for testing
|
||||
# insert admin user
|
||||
accounts = Application.get_env(:myapp, :admin_accounts)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user