1
0
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:
2018-01-25 19:57:36 -06:00
parent d6ec930370
commit 605e4ba94b
9 changed files with 121 additions and 23 deletions

View File

@@ -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
}
}
```

View File

@@ -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

View File

@@ -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,

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View 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

View File

@@ -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)