diff --git a/.tool-versions b/.tool-versions index 0f420050..95179337 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,3 +1,3 @@ -elixir 1.14.4-otp-25 -erlang 25.3.1 +elixir 1.17.3-otp-27 +erlang 27.1.2 php 8.3.7 diff --git a/Dockerfile b/Dockerfile index d7b70934..adc5f7bf 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM elixir:1.14.0 +FROM elixir:1.17.3 # install php RUN curl -sSL https://packages.sury.org/php/README.txt | bash -x RUN apt update diff --git a/config/runtime.exs b/config/runtime.exs index 492f30fd..61fded12 100644 --- a/config/runtime.exs +++ b/config/runtime.exs @@ -407,6 +407,16 @@ end ##### PROXY REPO CONFIGURATIONS ##### +bbc_parser_poolboy_config = [ + name: {:local, :bbc_parser}, + worker_module: EpochtalkServer.BBCParser, + size: 5, + max_overflow: 2, + strategy: :fifo +] + +config :epochtalk_server, bbc_parser_poolboy_config: bbc_parser_poolboy_config + # conditionally show debug logs in prod if config_env() == :prod do logger_level = diff --git a/lib/epochtalk_server/application.ex b/lib/epochtalk_server/application.ex index 4535c59e..d9de576b 100644 --- a/lib/epochtalk_server/application.ex +++ b/lib/epochtalk_server/application.ex @@ -21,6 +21,8 @@ defmodule EpochtalkServer.Application do EpochtalkServer.Repo, # Start the Smf repository EpochtalkServer.SmfRepo, + # Start the BBC Parser + :poolboy.child_spec(:bbc_parser, bbc_parser_poolboy_config()), # Start Role Cache EpochtalkServer.Cache.Role, # Warm frontend_config variable (referenced by api controllers) @@ -68,4 +70,7 @@ defmodule EpochtalkServer.Application do # fetch redix config defp redix_config(), do: Application.get_env(:epochtalk_server, :redix) + + defp bbc_parser_poolboy_config, + do: Application.get_env(:epochtalk_server, :bbc_parser_poolboy_config) end diff --git a/lib/epochtalk_server/bbc_parser.ex b/lib/epochtalk_server/bbc_parser.ex new file mode 100644 index 00000000..279ab6d4 --- /dev/null +++ b/lib/epochtalk_server/bbc_parser.ex @@ -0,0 +1,74 @@ +defmodule EpochtalkServer.BBCParser do + use GenServer + require Logger + alias Porcelain.Process, as: Proc + @timeout 1_000 + + @moduledoc """ + `BBCParser` genserver, runs interactive php shell to call bbcode parser + """ + + ## === genserver functions ==== + + @impl true + def init(:ok), do: {:ok, load()} + + @impl true + def handle_call({:parse, ""}, _from, {proc, pid}), + do: {:reply, "", {proc, pid}} + + def handle_call({:parse, bbcode_data}, _from, {proc, pid}) when is_binary(bbcode_data) do + Proc.send_input(proc, "echo parse_bbc('#{bbcode_data}');\n") + + parsed = + receive do + {^pid, :data, :out, data} -> + Logger.debug(data) + data + end + + {:reply, parsed, {proc, pid}} + end + + ## === parser api functions ==== + + @doc """ + Start genserver and create a reference for supervision tree + """ + def start_link(_opts), do: GenServer.start_link(__MODULE__, :ok) + + @doc """ + Uses poolboy to call parser + """ + def async_parse(bbcode_data) do + :poolboy.transaction( + :bbc_parser, + fn pid -> + try do + Logger.debug("#{__MODULE__}(ASYNC PARSE): #{inspect(pid)}") + GenServer.call(pid, {:parse, bbcode_data}, @timeout) + catch + e, r -> + Logger.debug("poolboy transaction caught error: #{inspect(e)}, #{inspect(r)}") + :ok + end + end, + @timeout + ) + end + + ## === private functions ==== + + # returns loaded interactive php shell + defp load() do + proc = %Proc{pid: pid} = Porcelain.spawn_shell("php -a", in: :receive, out: {:send, self()}) + Proc.send_input(proc, "require 'parsing.php';\n") + Logger.debug("#{__MODULE__}(LOAD): #{inspect(pid)}") + # clear initial php interactive shell message + receive do + {^pid, :data, :out, data} -> Logger.debug("#{__MODULE__}: #{inspect(data)}") + end + + {proc, pid} + end +end diff --git a/lib/epochtalk_server_web/controllers/board.ex b/lib/epochtalk_server_web/controllers/board.ex index ce4008ab..445d2bb5 100644 --- a/lib/epochtalk_server_web/controllers/board.ex +++ b/lib/epochtalk_server_web/controllers/board.ex @@ -103,9 +103,6 @@ defmodule EpochtalkServerWeb.Controllers.Board do 400, "Error, cannot convert slug: board does not exist" ) - - _ -> - ErrorHelpers.render_json_error(conn, 400, "Error, cannot convert board slug to id") end end diff --git a/lib/epochtalk_server_web/controllers/image_reference.ex b/lib/epochtalk_server_web/controllers/image_reference.ex index 1f73917c..9a40520a 100644 --- a/lib/epochtalk_server_web/controllers/image_reference.ex +++ b/lib/epochtalk_server_web/controllers/image_reference.ex @@ -63,8 +63,6 @@ defmodule EpochtalkServerWeb.Controllers.ImageReference do # checksum <- Validate.cast(attrs, "checksum", :string, required: true), file_type <- Validate.cast(attrs, "file_type", :string, required: true) do %{length: length, type: file_type} - else - _ -> %{error: "Invalid attrs"} end end end diff --git a/lib/epochtalk_server_web/controllers/mention.ex b/lib/epochtalk_server_web/controllers/mention.ex index 6a683cdc..e3a25e19 100644 --- a/lib/epochtalk_server_web/controllers/mention.ex +++ b/lib/epochtalk_server_web/controllers/mention.ex @@ -27,8 +27,7 @@ defmodule EpochtalkServerWeb.Controllers.Mention do pagination_data: data, extended: extended }), - else: - ({:auth, nil} -> - ErrorHelpers.render_json_error(conn, 400, "Not logged in, cannot page mentions")) + else: ({:auth, nil} -> + ErrorHelpers.render_json_error(conn, 400, "Not logged in, cannot page mentions")) end end diff --git a/lib/epochtalk_server_web/controllers/moderation_log.ex b/lib/epochtalk_server_web/controllers/moderation_log.ex index 888f8990..fb032aca 100644 --- a/lib/epochtalk_server_web/controllers/moderation_log.ex +++ b/lib/epochtalk_server_web/controllers/moderation_log.ex @@ -4,7 +4,6 @@ defmodule EpochtalkServerWeb.Controllers.ModerationLog do @moduledoc """ Controller For `ModerationLog` related API requests """ - alias EpochtalkServer.Auth.Guardian alias EpochtalkServer.Models.ModerationLog alias EpochtalkServerWeb.ErrorHelpers alias EpochtalkServerWeb.Helpers.Validate @@ -14,16 +13,12 @@ defmodule EpochtalkServerWeb.Controllers.ModerationLog do Used to page `ModerationLog` models for moderation log view` """ def page(conn, attrs) do - with {:auth, true} <- {:auth, Guardian.Plug.authenticated?(conn)}, - :ok <- ACL.allow!(conn, "moderationLogs.page"), + with :ok <- ACL.allow!(conn, "moderationLogs.page"), page <- Validate.cast(attrs, "page", :integer, min: 1), limit <- Validate.cast(attrs, "limit", :integer, min: 1), {:ok, moderation_logs, data} <- ModerationLog.page(attrs, page, per_page: limit) do render(conn, :page, %{moderation_logs: moderation_logs, pagination_data: data}) else - {:auth, false} -> - ErrorHelpers.render_json_error(conn, 400, "Not logged in, cannot page moderation log") - {:error, data} -> ErrorHelpers.render_json_error(conn, 400, data) diff --git a/lib/epochtalk_server_web/controllers/notification.ex b/lib/epochtalk_server_web/controllers/notification.ex index 8a9ffbdf..7e05df9e 100644 --- a/lib/epochtalk_server_web/controllers/notification.ex +++ b/lib/epochtalk_server_web/controllers/notification.ex @@ -14,23 +14,16 @@ defmodule EpochtalkServerWeb.Controllers.Notification do Used to retrieve `Notification` counts for a specific `User` """ def counts(conn, attrs) do - with {:auth, %{} = user} <- {:auth, Guardian.Plug.current_resource(conn)}, + with user <- Guardian.Plug.current_resource(conn), :ok <- ACL.allow!(conn, "notifications.counts"), max <- Validate.cast(attrs, "max", :integer, min: 1) do render(conn, :counts, data: Notification.counts_by_user_id(user.id, max: max || 99)) else - {:auth, nil} -> - ErrorHelpers.render_json_error( - conn, - 400, - "Not logged in, cannot fetch notification counts" - ) - - {:access, false} -> + _ -> ErrorHelpers.render_json_error( conn, - 400, - "Not logged in, cannot fetch notification counts" + 500, + "Something went wrong, cannot fetch notification counts" ) end end @@ -39,19 +32,12 @@ defmodule EpochtalkServerWeb.Controllers.Notification do Used to dismiss `Notification` counts for a specific `User` """ def dismiss(conn, %{"id" => id}) do - with {:auth, %{} = user} <- {:auth, Guardian.Plug.current_resource(conn)}, + with user <- Guardian.Plug.current_resource(conn), :ok <- ACL.allow!(conn, "notifications.dismiss"), {_count, nil} <- Notification.dismiss(id) do EpochtalkServerWeb.Endpoint.broadcast("user:#{user.id}", "refreshMentions", %{}) render(conn, :dismiss, success: true) else - {:auth, nil} -> - ErrorHelpers.render_json_error( - conn, - 400, - "Not logged in, cannot dismiss notification counts" - ) - _ -> ErrorHelpers.render_json_error( conn, @@ -62,19 +48,12 @@ defmodule EpochtalkServerWeb.Controllers.Notification do end def dismiss(conn, %{"type" => type}) do - with {:auth, %{} = user} <- {:auth, Guardian.Plug.current_resource(conn)}, + with user <- Guardian.Plug.current_resource(conn), :ok <- ACL.allow!(conn, "notifications.dismiss"), {_count, nil} <- Notification.dismiss_type_by_user_id(user.id, type) do EpochtalkServerWeb.Endpoint.broadcast("user:#{user.id}", "refreshMentions", %{}) render(conn, :dismiss, success: true) else - {:auth, nil} -> - ErrorHelpers.render_json_error( - conn, - 400, - "Not logged in, cannot dismiss notification counts" - ) - {:error, :invalid_notification_type} -> ErrorHelpers.render_json_error(conn, 400, "Cannot dismiss, invalid notification type") diff --git a/lib/epochtalk_server_web/controllers/poll.ex b/lib/epochtalk_server_web/controllers/poll.ex index 7fea5cc5..a8afa708 100644 --- a/lib/epochtalk_server_web/controllers/poll.ex +++ b/lib/epochtalk_server_web/controllers/poll.ex @@ -193,9 +193,6 @@ defmodule EpochtalkServerWeb.Controllers.Poll do "Account must be active to modify lock on poll" ) - {:error, data} -> - ErrorHelpers.render_json_error(conn, 400, data) - _ -> ErrorHelpers.render_json_error(conn, 400, "Error, cannot lock poll") end @@ -316,9 +313,6 @@ defmodule EpochtalkServerWeb.Controllers.Poll do poll <- Poll.by_thread(thread_id) do render(conn, :poll, %{poll: poll, has_voted: false}) else - {:valid_answers_list, false} -> - ErrorHelpers.render_json_error(conn, 400, "Error, 'answer_ids' must be a list") - {:can_read, {:ok, false}} -> ErrorHelpers.render_json_error( conn, @@ -355,14 +349,8 @@ defmodule EpochtalkServerWeb.Controllers.Poll do {:board_banned, {:ok, true}} -> ErrorHelpers.render_json_error(conn, 403, "Unauthorized, you are banned from this board") - {:error, :board_does_not_exist} -> - ErrorHelpers.render_json_error(conn, 400, "Error, board does not exist") - - {:error, data} -> - ErrorHelpers.render_json_error(conn, 400, data) - _ -> - ErrorHelpers.render_json_error(conn, 400, "Error, cannot cast vote") + ErrorHelpers.render_json_error(conn, 400, "Error, cannot delete vote") end end diff --git a/lib/epochtalk_server_web/controllers/post.ex b/lib/epochtalk_server_web/controllers/post.ex index 667fd2a1..6adaa4fd 100644 --- a/lib/epochtalk_server_web/controllers/post.ex +++ b/lib/epochtalk_server_web/controllers/post.ex @@ -32,7 +32,7 @@ defmodule EpochtalkServerWeb.Controllers.Post do @max_post_title_length 255 - plug :check_proxy when action in [:by_thread] + plug :check_proxy when action in [:by_thread, :by_username] @doc """ Used to create posts @@ -420,9 +420,6 @@ defmodule EpochtalkServerWeb.Controllers.Post do Validate.cast(attrs, "body", :string, required: true, max: post_max_length, min: 1), parsed_body <- Parse.markdown(body) do render(conn, :preview, %{parsed_body: parsed_body}) - else - _ -> - ErrorHelpers.render_json_error(conn, 400, "Error, cannot generate preview") end end @@ -542,6 +539,11 @@ defmodule EpochtalkServerWeb.Controllers.Post do conn end + :by_username -> + conn + |> proxy_by_username(conn.params) + |> halt() + _ -> conn end @@ -549,6 +551,27 @@ defmodule EpochtalkServerWeb.Controllers.Post do conn end + defp proxy_by_username(conn, attrs) do + # Parameter Validation + with user_id <- Validate.cast(attrs, "id", :integer, required: true), + page <- Validate.cast(attrs, "page", :integer, default: 1, min: 1), + limit <- Validate.cast(attrs, "limit", :integer, default: 25, min: 1, max: 100), + desc <- Validate.cast(attrs, "desc", :boolean, default: true), + {:ok, posts, data} <- + ProxyConversion.build_model("posts.by_user", user_id, page, limit, desc) do + render(conn, :proxy_by_username, %{ + posts: posts, + count: data.total_records, + limit: data.per_page, + page: data.page, + desc: desc + }) + else + _ -> + ErrorHelpers.render_json_error(conn, 400, "Error, cannot get posts by username") + end + end + defp proxy_by_thread(conn, attrs) do with thread_id <- Validate.cast(attrs, "thread_id", :integer, required: true), page <- Validate.cast(attrs, "page", :integer, default: 1), diff --git a/lib/epochtalk_server_web/controllers/preference.ex b/lib/epochtalk_server_web/controllers/preference.ex index 0e7405a2..ea7e1564 100644 --- a/lib/epochtalk_server_web/controllers/preference.ex +++ b/lib/epochtalk_server_web/controllers/preference.ex @@ -14,8 +14,11 @@ defmodule EpochtalkServerWeb.Controllers.Preference do def preferences(conn, _attrs) do with {:auth, %{} = user} <- {:auth, Guardian.Plug.current_resource(conn)}, do: render(conn, :preferences, preferences: Preference.by_user_id(user.id)), - else: - ({:auth, nil} -> - ErrorHelpers.render_json_error(conn, 400, "Not logged in, cannot fetch preferences")) + else: ({:auth, nil} -> + ErrorHelpers.render_json_error( + conn, + 400, + "Not logged in, cannot fetch preferences" + )) end end diff --git a/lib/epochtalk_server_web/controllers/thread.ex b/lib/epochtalk_server_web/controllers/thread.ex index 8f252b83..c4762a1c 100644 --- a/lib/epochtalk_server_web/controllers/thread.ex +++ b/lib/epochtalk_server_web/controllers/thread.ex @@ -28,7 +28,7 @@ defmodule EpochtalkServerWeb.Controllers.Thread do alias EpochtalkServer.Models.Mention alias EpochtalkServerWeb.Helpers.ProxyConversion - plug :check_proxy when action in [:by_board, :slug_to_id, :viewed, :recent] + plug :check_proxy when action in [:by_board, :by_username, :slug_to_id, :viewed, :recent] @doc """ Used to retrieve recent threads @@ -38,8 +38,6 @@ defmodule EpochtalkServerWeb.Controllers.Thread do user_priority <- ACL.get_user_priority(conn), threads <- Thread.recent(user, user_priority) do render(conn, :recent, %{threads: threads}) - else - _ -> ErrorHelpers.render_json_error(conn, 400, "Error, cannot fetch recent threads") end end @@ -217,14 +215,8 @@ defmodule EpochtalkServerWeb.Controllers.Thread do {:can_read, {:error, :board_does_not_exist}} -> ErrorHelpers.render_json_error(conn, 400, "Read error, board does not exist") - {:board_banned, {:ok, true}} -> - ErrorHelpers.render_json_error(conn, 403, "Unauthorized, you are banned from this board") - {:has_threads, false} -> ErrorHelpers.render_json_error(conn, 404, "Error, requested threads not found in board") - - _ -> - ErrorHelpers.render_json_error(conn, 400, "Error, cannot get threads by board") end end @@ -343,9 +335,6 @@ defmodule EpochtalkServerWeb.Controllers.Thread do "Account must be active to unwatch thread" ) - {:error, data} -> - ErrorHelpers.render_json_error(conn, 400, data) - _ -> ErrorHelpers.render_json_error(conn, 400, "Error, cannot unwatch thread") end @@ -404,9 +393,6 @@ defmodule EpochtalkServerWeb.Controllers.Thread do "Account must be active to modify lock on thread" ) - {:error, data} -> - ErrorHelpers.render_json_error(conn, 400, data) - _ -> ErrorHelpers.render_json_error(conn, 400, "Error, cannot lock thread") end @@ -465,9 +451,6 @@ defmodule EpochtalkServerWeb.Controllers.Thread do "Account must be active to modify sticky on thread" ) - {:error, data} -> - ErrorHelpers.render_json_error(conn, 400, data) - _ -> ErrorHelpers.render_json_error(conn, 400, "Error, cannot sticky thread") end @@ -641,9 +624,6 @@ defmodule EpochtalkServerWeb.Controllers.Thread do 400, "Error, cannot convert slug, thread does not exist" ) - - _ -> - ErrorHelpers.render_json_error(conn, 400, "Error, cannot convert thread slug to id") end end @@ -666,7 +646,7 @@ defmodule EpochtalkServerWeb.Controllers.Thread do |> send_resp(200, []) |> halt() else - {:error, :board_does_not_exist} -> + {:can_read, {:error, :board_does_not_exist}} -> ErrorHelpers.render_json_error( conn, 400, @@ -801,11 +781,40 @@ defmodule EpochtalkServerWeb.Controllers.Thread do |> halt() end + # check proxy for :recent action + defp check_proxy(%{private: %{phoenix_action: :by_username}} = conn, _) do + conn + |> proxy_by_username(conn.params) + |> halt() + end + # check proxy default defp check_proxy(%{private: %{phoenix_action: _}} = conn, _) do conn end + defp proxy_by_username(conn, attrs) do + # Parameter Validation + with user_id <- Validate.cast(attrs, "id", :integer, required: true), + page <- Validate.cast(attrs, "page", :integer, default: 1, min: 1), + limit <- Validate.cast(attrs, "limit", :integer, default: 25, min: 1, max: 100), + desc <- Validate.cast(attrs, "desc", :boolean, default: true), + {:ok, threads, data} <- + ProxyConversion.build_model("threads.by_user", user_id, page, limit, desc) do + render(conn, :proxy_by_username, %{ + threads: threads, + next: data.next, + prev: data.prev, + limit: data.per_page, + page: data.page, + desc: desc + }) + else + _ -> + ErrorHelpers.render_json_error(conn, 400, "Error, cannot get threads by username") + end + end + defp proxy_by_board(conn, attrs) do with board_id <- Validate.cast(attrs, "board_id", :integer, required: true), page <- Validate.cast(attrs, "page", :integer, default: 1), @@ -841,8 +850,6 @@ defmodule EpochtalkServerWeb.Controllers.Thread do defp proxy_recent(conn, _attrs) do with threads <- ProxyConversion.build_model("threads.recent") do render(conn, :recent, %{threads: threads}) - else - _ -> ErrorHelpers.render_json_error(conn, 400, "Error, cannot fetch recent threads") end end diff --git a/lib/epochtalk_server_web/controllers/user.ex b/lib/epochtalk_server_web/controllers/user.ex index 07f2f452..184d9609 100644 --- a/lib/epochtalk_server_web/controllers/user.ex +++ b/lib/epochtalk_server_web/controllers/user.ex @@ -17,6 +17,9 @@ defmodule EpochtalkServerWeb.Controllers.User do alias EpochtalkServerWeb.CustomErrors.InvalidPayload alias EpochtalkServerWeb.Helpers.ACL alias EpochtalkServerWeb.Helpers.Validate + alias EpochtalkServerWeb.Helpers.ProxyConversion + + plug :check_proxy when action in [:find] @doc """ Used to check if a username has already been taken @@ -94,10 +97,6 @@ defmodule EpochtalkServerWeb.Controllers.User do {:ok, _email} <- Mailer.send_confirm_account(user) do render(conn, :register_with_verify, user: user) else - # error in user.create - {:error, data} -> - ErrorHelpers.render_json_error(conn, 400, data) - # error email failed to send {:error, :not_delivered} -> ErrorHelpers.render_json_error( @@ -106,6 +105,10 @@ defmodule EpochtalkServerWeb.Controllers.User do "Sending of account confirmation email failed, mailer is not properly configured." ) + # error in user.create + {:error, data} -> + ErrorHelpers.render_json_error(conn, 400, data) + # Catch all for any other errors _ -> ErrorHelpers.render_json_error(conn, 500, "There was an issue registering") @@ -139,12 +142,6 @@ defmodule EpochtalkServerWeb.Controllers.User do 500, "There was an error banning malicious user, upon confirming account" ) - - {:error, data} -> - ErrorHelpers.render_json_error(conn, 400, data) - - _ -> - ErrorHelpers.render_json_error(conn, 500, "There was an issue registering") end end @@ -179,17 +176,11 @@ defmodule EpochtalkServerWeb.Controllers.User do show_hidden: show_hidden }) else - {:error, :user_not_found} -> - ErrorHelpers.render_json_error(conn, 400, "Account not found") - - {:error, data} -> - ErrorHelpers.render_json_error(conn, 400, data) - {:view_deleted, false} -> ErrorHelpers.render_json_error(conn, 400, "Account not found") - _ -> - ErrorHelpers.render_json_error(conn, 500, "There was an issue finding user") + {:error, _} -> + ErrorHelpers.render_json_error(conn, 400, "Account not found") end end @@ -208,16 +199,13 @@ defmodule EpochtalkServerWeb.Controllers.User do Logs out the logged in `User` """ def logout(conn, _attrs) do - with {:auth, true} <- {:auth, Guardian.Plug.authenticated?(conn)}, - user <- Guardian.Plug.current_resource(conn), + with user <- Guardian.Plug.current_resource(conn), token <- Guardian.Plug.current_token(conn), {:ok, conn} <- Session.delete(conn) do EpochtalkServerWeb.Endpoint.broadcast("user:#{user.id}", "logout", %{token: token}) render(conn, :data, data: %{success: true}) else - {:auth, false} -> ErrorHelpers.render_json_error(conn, 400, "Not logged in") - {:error, error} -> ErrorHelpers.render_json_error(conn, 500, error) - _ -> ErrorHelpers.render_json_error(conn, 500, "There was an issue signing out") + {:error, data} -> ErrorHelpers.render_json_error(conn, 500, data) end end @@ -262,11 +250,28 @@ defmodule EpochtalkServerWeb.Controllers.User do {:error, :unban_error} -> ErrorHelpers.render_json_error(conn, 500, "There was an issue unbanning user, upon login") + end + end + + def login(_conn, _attrs), do: raise(InvalidPayload) + + ## === Private Helper Functions === + + defp check_proxy(conn, _) do + case conn.private.phoenix_action do + :find -> + conn + |> proxy_find(conn.params) + |> halt() _ -> - ErrorHelpers.render_json_error(conn, 500, "There was an issue while attempting to login") + conn end end - def login(_conn, _attrs), do: raise(InvalidPayload) + defp proxy_find(conn, attrs) do + with user <- ProxyConversion.build_model("user.find", attrs["id"]) do + render(conn, :find_proxy, %{user: user}) + end + end end diff --git a/lib/epochtalk_server_web/helpers/proxy_conversion.ex b/lib/epochtalk_server_web/helpers/proxy_conversion.ex index d138a9f7..1e9bd2e5 100644 --- a/lib/epochtalk_server_web/helpers/proxy_conversion.ex +++ b/lib/epochtalk_server_web/helpers/proxy_conversion.ex @@ -28,6 +28,19 @@ defmodule EpochtalkServerWeb.Helpers.ProxyConversion do end end + def build_model(model_type, id, page, per_page, desc) when is_integer(id) do + case model_type do + "threads.by_user" -> + build_threads_by_user(id, page, per_page, desc) + + "posts.by_user" -> + build_posts_by_user(id, page, per_page, desc) + + _ -> + build_model(nil, nil, nil, nil) + end + end + def build_model(model_type, id) do case model_type do "category" -> @@ -45,6 +58,9 @@ defmodule EpochtalkServerWeb.Helpers.ProxyConversion do "poll.by_thread" -> build_poll(id) + "user.find" -> + build_user(id) + _ -> build_model(nil, nil, nil, nil) end @@ -69,6 +85,47 @@ defmodule EpochtalkServerWeb.Helpers.ProxyConversion do end end + def build_user(user_id) do + from(u in "smf_members", where: u.id_member == ^user_id) + |> join(:left, [u], a in "smf_attachments", + on: u.id_member == a.id_member and a.attachmentType == 1 + ) + |> join(:left, [u], m in "smf_membergroups", on: u.id_group != 0 and u.id_group == m.id_group) + |> join(:left, [u], g in "smf_membergroups", on: u.id_post_group == g.id_group) + |> select([u, a, m, g], %{ + activity: u.activity, + created_at: u.dateRegistered * 1000, + dob: u.birthdate, + gender: u.gender, + id: u.id_member, + language: nil, + location: u.location, + merit: u.merit, + id_group: u.id_group, + id_post_group: u.id_post_group, + signature: u.signature, + post_count: u.posts, + name: u.realName, + username: u.realName, + title: u.usertitle, + website: u.websiteUrl, + last_login: u.lastLogin * 1000, + show_online: u.showOnline, + group_name: m.groupName, + group_name_2: g.groupName, + group_color: m.onlineColor, + group_color_2: g.onlineColor, + avatar: + fragment( + "if(? <>'',concat('https://bitcointalk.org/avatars/',?),ifnull(concat('https://bitcointalk.org/useravatars/',?),''))", + u.avatar, + u.avatar, + a.filename + ) + }) + |> SmfRepo.one() + end + def build_poll(thread_id) do from(t in "smf_topics", where: t.id_topic == ^thread_id @@ -208,11 +265,23 @@ defmodule EpochtalkServerWeb.Helpers.ProxyConversion do ) |> join(:left, [b], m in "smf_messages", on: b.id_last_msg == m.id_msg) |> join(:left, [b, m], t in "smf_topics", on: m.id_topic == t.id_topic) - |> select([b, m, t], %{ + |> join(:left, [b, m, t], u in "smf_members", on: m.id_member == u.id_member) + |> join(:left, [b, m, t, u], a in "smf_attachments", + on: m.id_member == a.id_member and a.attachmentType == 1 + ) + |> select([b, m, t, u, a], %{ id: b.id_board, last_post_created_at: m.posterTime * 1000, last_post_position: t.numReplies, last_post_username: m.posterName, + last_post_user_id: m.id_member, + last_post_avatar: + fragment( + "if(? <>'',concat('https://bitcointalk.org/avatars/',?),ifnull(concat('https://bitcointalk.org/useravatars/',?),''))", + u.avatar, + u.avatar, + a.filename + ), last_thread_created_at: t.id_member_started, last_thread_id: t.id_topic, last_thread_post_count: t.numReplies, @@ -311,7 +380,7 @@ defmodule EpochtalkServerWeb.Helpers.ProxyConversion do first_post_id: t.id_first_msg, last_post_id: t.id_last_msg, started_user_id: t.id_member_started, - updated_user_id: t.id_member_updated, + last_post_user_id: t.id_member_updated, moderated: t.selfModerated, post_count: t.numReplies, title: f.subject, @@ -334,7 +403,7 @@ defmodule EpochtalkServerWeb.Helpers.ProxyConversion do last_viewed: nil, is_proxy: true }) - |> ProxyPagination.page_simple(count_query, page, per_page: per_page) + |> ProxyPagination.page_simple(count_query, page, per_page: per_page, desc: true) end def build_thread(id) do @@ -452,7 +521,7 @@ defmodule EpochtalkServerWeb.Helpers.ProxyConversion do title: u.usertitle } }) - |> ProxyPagination.page_simple(count_query, page, per_page: per_page) + |> ProxyPagination.page_simple(count_query, page, per_page: per_page, desc: true) |> case do {:ok, [], _} -> {:error, "Posts not found for thread_id: #{id}"} @@ -462,6 +531,70 @@ defmodule EpochtalkServerWeb.Helpers.ProxyConversion do end end + def build_posts_by_user(id, page, per_page, desc) do + %{id_board_blacklist: id_board_blacklist} = + Application.get_env(:epochtalk_server, :proxy_config) + + direction = if desc, do: :desc, else: :asc + + count_query = + from u in "smf_members", + where: u.id_member == ^id, + select: %{count: u.posts} + + from(m in "smf_messages", + limit: ^per_page, + where: m.id_member == ^id and m.id_board not in ^id_board_blacklist, + order_by: [{^direction, m.id_msg}] + ) + |> select([m], %{ + id: m.id_msg, + thread_id: m.id_topic, + board_id: m.id_board, + thread_title: m.subject, + thread_slug: m.id_topic, + position: 0, + body_html: m.body, + updated_at: m.modifiedTime, + created_at: m.posterTime * 1000, + user: %{ + id: m.id_member + } + }) + |> ProxyPagination.page_simple(count_query, page, per_page: per_page, desc: desc) + end + + def build_threads_by_user(id, page, per_page, desc) do + %{id_board_blacklist: id_board_blacklist} = + Application.get_env(:epochtalk_server, :proxy_config) + + direction = if desc, do: :desc, else: :asc + + from(t in "smf_topics", + where: t.id_member_started == ^id and t.id_board not in ^id_board_blacklist, + order_by: [{^direction, t.id_topic}] + ) + |> join(:left, [t], f in "smf_messages", on: t.id_first_msg == f.id_msg) + |> join(:left, [t], l in "smf_messages", on: t.id_last_msg == l.id_msg) + |> select([t, f, l], %{ + thread_id: t.id_topic, + thread_slug: t.id_topic, + board_id: t.id_board, + sticky: t.isSticky, + locked: t.locked, + user: %{id: t.id_member_started, deleted: false}, + moderated: t.selfModerated, + post_count: t.numReplies, + thread_title: f.subject, + body: f.body, + created_at: f.posterTime * 1000, + updated_at: l.posterTime * 1000, + board_visible: true, + is_proxy: true + }) + |> ProxyPagination.page_next_prev(page, per_page: per_page, desc: desc) + end + defp return_tuple(object) do if length(object) > 1 do {:ok, object} diff --git a/lib/epochtalk_server_web/helpers/proxy_pagination.ex b/lib/epochtalk_server_web/helpers/proxy_pagination.ex index 6769893f..a2d239ea 100644 --- a/lib/epochtalk_server_web/helpers/proxy_pagination.ex +++ b/lib/epochtalk_server_web/helpers/proxy_pagination.ex @@ -16,13 +16,14 @@ defmodule EpochtalkServerWeb.Helpers.ProxyPagination do iex> alias EpochtalkServerWeb.Helpers.Pagination iex> Mention ...> |> order_by(asc: :id) - ...> |> Pagination.page_simple(1, per_page: 25) + ...> |> Pagination.page_simple(1, per_page: 25, desc: true) {:ok, [], %{next: false, page: 1, per_page: 25, prev: false, total_pages: 1, - total_records: 0}} + total_records: 0, + desc: true}} iex> Invitation ...> |> order_by(desc: :email) ...> |> Pagination.page_simple(1, per_page: 10) @@ -31,43 +32,50 @@ defmodule EpochtalkServerWeb.Helpers.ProxyPagination do per_page: 10, prev: false, total_pages: 1, - total_records: 0}} + total_records: 0, + desc: true}} """ @spec page_simple( query :: Ecto.Queryable.t(), count_query :: Ecto.Queryable.t(), page :: integer | String.t() | nil, - per_page: integer | String.t() | nil - ) :: {:ok, list :: [term()] | [], pagination_data :: map()} - def page_simple(query, count_query, nil, per_page: nil), - do: page_simple(query, count_query, 1, per_page: 15) + per_page: integer | String.t() | nil, + desc: boolean + ) :: {:ok, list :: [term()] | [], pagination_data :: map()} | {:error, data :: any()} + def page_simple(query, count_query, nil, per_page: nil, desc: desc), + do: page_simple(query, count_query, 1, per_page: 15, desc: desc) - def page_simple(query, count_query, page, per_page: nil) when is_integer(page), - do: page_simple(query, count_query, page, per_page: 15) + def page_simple(query, count_query, page, per_page: nil, desc: desc) when is_integer(page), + do: page_simple(query, count_query, page, per_page: 15, desc: desc) - def page_simple(query, count_query, nil, per_page: per_page) when is_integer(per_page), - do: page_simple(query, count_query, 1, per_page: per_page) + def page_simple(query, count_query, nil, per_page: per_page, desc: desc) + when is_integer(per_page), + do: page_simple(query, count_query, 1, per_page: per_page, desc: desc) - def page_simple(query, count_query, nil, per_page: per_page) when is_binary(per_page), - do: - page_simple(query, count_query, 1, - per_page: Validate.cast_str(per_page, :integer, key: "limit", min: 1) - ) + def page_simple(query, count_query, nil, per_page: per_page, desc: desc) + when is_binary(per_page), + do: + page_simple(query, count_query, 1, + per_page: Validate.cast_str(per_page, :integer, key: "limit", min: 1), + desc: desc + ) - def page_simple(query, count_query, page, per_page: nil) when is_binary(page), + def page_simple(query, count_query, page, per_page: nil, desc: desc) when is_binary(page), do: page_simple(query, count_query, Validate.cast_str(page, :integer, key: "page", min: 1), - per_page: 15 + per_page: 15, + desc: desc ) - def page_simple(query, count_query, page, per_page: per_page) + def page_simple(query, count_query, page, per_page: per_page, desc: desc) when is_binary(page) and is_binary(per_page), do: page_simple(query, count_query, Validate.cast_str(page, :integer, key: "page", min: 1), - per_page: Validate.cast_str(per_page, :integer, key: "limit", min: 1) + per_page: Validate.cast_str(per_page, :integer, key: "limit", min: 1), + desc: desc ) - def page_simple(query, count_query, page, per_page: per_page) do + def page_simple(query, count_query, page, per_page: per_page, desc: desc) do options = [prefix: "public"] total_records = @@ -85,12 +93,43 @@ defmodule EpochtalkServerWeb.Helpers.ProxyPagination do page: page, per_page: per_page, total_records: total_records, - total_pages: total_pages + total_pages: total_pages, + desc: desc + } + + {:ok, result, pagination_data} + end + + def page_next_prev(query, page, per_page: per_page, desc: desc) do + # query one more page to calculate if next page exists + result = records(query, page, nil, per_page + 1) + + next = length(result) > per_page + + pagination_data = %{ + next: next, + prev: page > 1, + page: page, + per_page: per_page, + desc: desc } + # remove extra element + result = + if next, + do: result |> Enum.reverse() |> tl() |> Enum.reverse(), + else: result + {:ok, result, pagination_data} end + defp records(query, page, total_pages, per_page) when is_nil(total_pages) do + query + |> limit(^per_page) + |> offset(^(per_page * (page - 1))) + |> SmfRepo.all() + end + defp records(_, page, total_pages, _) when page > total_pages, do: [] defp records(query, page, _, per_page) do diff --git a/lib/epochtalk_server_web/json/post_json.ex b/lib/epochtalk_server_web/json/post_json.ex index d260a861..6e739165 100644 --- a/lib/epochtalk_server_web/json/post_json.ex +++ b/lib/epochtalk_server_web/json/post_json.ex @@ -152,8 +152,6 @@ defmodule EpochtalkServerWeb.Controllers.PostJSON do } end - ## === Private Helper Functions === - @doc """ Renders all `Post` for a particular `User`. """ @@ -181,6 +179,46 @@ defmodule EpochtalkServerWeb.Controllers.PostJSON do } end + @doc """ + Renders all `Post` for a particular `User`. + """ + def proxy_by_username(%{ + posts: posts, + count: count, + limit: limit, + page: page, + desc: desc + }) + when is_list(posts) do + posts = + posts + |> Enum.map(&format_proxy_post_data_for_by_thread(&1)) + + %{ + posts: posts, + count: count, + limit: limit, + page: page, + desc: desc + } + end + + def proxy_by_username(%{ + posts: posts, + count: count, + limit: limit, + page: page, + desc: desc + }), + do: + proxy_by_username(%{ + posts: [posts], + count: count, + limit: limit, + page: page, + desc: desc + }) + ## === Public Helper Functions === def handle_deleted_posts(posts, thread, user, authed_user_priority, view_deleted_posts) do @@ -407,27 +445,19 @@ defmodule EpochtalkServerWeb.Controllers.PostJSON do end defp format_proxy_post_data_for_by_thread(post) do - body = String.replace(post.body || post.body_html, "'", "\'") + body = String.replace(Map.get(post, :body) || Map.get(post, :body_html), "'", "\'") - %Porcelain.Result{out: parsed_body, status: _status} = - Porcelain.shell("php -r \"require 'parsing.php'; ECHO parse_bbc('" <> body <> "');\"") + parsed_body = EpochtalkServer.BBCParser.async_parse(body) signature = - if post.user.signature, + if Map.get(post.user, :signature), do: String.replace(post.user.signature, "'", "\'"), else: nil parsed_signature = - if signature do - %Porcelain.Result{out: parsed_sig, status: _status} = - Porcelain.shell( - "php -r \"require 'parsing.php'; ECHO parse_bbc('" <> signature <> "');\"" - ) - - parsed_sig - else - nil - end + if signature, + do: EpochtalkServer.BBCParser.async_parse(signature), + else: nil user = post.user |> Map.put(:signature, parsed_signature) post |> Map.put(:body_html, parsed_body) |> Map.put(:user, user) diff --git a/lib/epochtalk_server_web/json/thread_json.ex b/lib/epochtalk_server_web/json/thread_json.ex index 6ab0e987..c8bf425b 100644 --- a/lib/epochtalk_server_web/json/thread_json.ex +++ b/lib/epochtalk_server_web/json/thread_json.ex @@ -199,6 +199,27 @@ defmodule EpochtalkServerWeb.Controllers.ThreadJSON do } end + @doc """ + Renders all `Post` for a particular `User`. + """ + def proxy_by_username(%{ + threads: threads, + next: next, + prev: prev, + limit: limit, + page: page, + desc: desc + }) do + %{ + posts: threads, + next: next, + prev: prev, + limit: limit, + page: page, + desc: desc + } + end + @doc """ Renders sticky `Thread`. @@ -277,7 +298,7 @@ defmodule EpochtalkServerWeb.Controllers.ThreadJSON do # handle deleted user thread = if thread.user_deleted, - do: thread |> Map.put(:user_id, '') |> Map.put(:username, ''), + do: thread |> Map.put(:user_id, "") |> Map.put(:username, ""), else: thread # format user output @@ -345,6 +366,5 @@ defmodule EpochtalkServerWeb.Controllers.ThreadJSON do thread |> Map.delete(:last_post_deleted) |> Map.delete(:last_post_user_deleted) - |> Map.delete(:last_post_user_id) end end diff --git a/lib/epochtalk_server_web/json/user_json.ex b/lib/epochtalk_server_web/json/user_json.ex index 139d313a..93456f53 100644 --- a/lib/epochtalk_server_web/json/user_json.ex +++ b/lib/epochtalk_server_web/json/user_json.ex @@ -13,6 +13,48 @@ defmodule EpochtalkServerWeb.Controllers.UserJSON do """ def user(%{user: user, token: token}), do: format_user_reply(user, token) + @doc """ + Renders formatted user JSON for find proxy + """ + def find_proxy(%{user: user}) do + parsed_signature = + if user.signature, + do: EpochtalkServer.BBCParser.async_parse(user.signature), + else: nil + + gender = + case Map.get(user, :gender) do + 1 -> "Male" + 2 -> "Female" + _ -> nil + end + + dob = + case d = Map.get(user, :dob) do + ~D[0001-01-01] -> nil + _ -> d + end + + last_active = calculate_last_active(user) + + position = user.group_name || user.group_name_2 + position_color = user.group_color || user.group_color_2 + + user + |> Map.put(:signature, parsed_signature) + |> Map.put(:gender, gender) + |> Map.put(:dob, dob) + |> Map.put(:last_active, last_active) + |> Map.put(:position, position) + |> Map.put(:position_color, position_color) + |> Map.delete(:last_login) + |> Map.delete(:show_online) + |> Map.delete(:group_name) + |> Map.delete(:group_name_2) + |> Map.delete(:group_color) + |> Map.delete(:group_color_2) + end + @doc """ Renders formatted user JSON for find """ @@ -142,4 +184,11 @@ defmodule EpochtalkServerWeb.Controllers.UserJSON do reply = if malicious_score, do: Map.put(reply, :malicious_score, malicious_score), else: reply reply end + + defp calculate_last_active(user) when is_map(user) do + {:ok, last_login} = DateTime.from_unix(user.last_login, :millisecond) + last_login_past_72_hours = DateTime.diff(DateTime.utc_now(), last_login, :hour) > 72 + + if user.show_online == 1 or last_login_past_72_hours, do: user.last_login + end end diff --git a/lib/epochtalk_server_web/router.ex b/lib/epochtalk_server_web/router.ex index dcdb48d2..98225547 100644 --- a/lib/epochtalk_server_web/router.ex +++ b/lib/epochtalk_server_web/router.ex @@ -65,6 +65,7 @@ defmodule EpochtalkServerWeb.Router do get "/admin/modlog", ModerationLog, :page get "/boards/movelist", Board, :movelist post "/images/s3/upload", ImageReference, :s3_request_upload + delete "/logout", User, :logout end scope "/api", EpochtalkServerWeb.Controllers do @@ -86,7 +87,6 @@ defmodule EpochtalkServerWeb.Router do post "/register", User, :register post "/login", User, :login post "/confirm", User, :confirm - delete "/logout", User, :logout end scope "/", EpochtalkServerWeb.Controllers do diff --git a/mix.exs b/mix.exs index 7c61b22c..de965665 100644 --- a/mix.exs +++ b/mix.exs @@ -5,7 +5,7 @@ defmodule EpochtalkServer.MixProject do [ app: :epochtalk_server, version: "0.1.0", - elixir: "~> 1.12", + elixir: "~> 1.17", elixirc_paths: elixirc_paths(Mix.env()), compilers: Mix.compilers(), start_permanent: Mix.env() == :prod, @@ -36,7 +36,7 @@ defmodule EpochtalkServer.MixProject do {:argon2_elixir, "~> 3.1.0"}, {:configparser_ex, "~> 4.0"}, {:corsica, "~> 1.3.0"}, - {:credo, "~> 1.6", only: [:dev, :test], runtime: false}, + {:credo, "~> 1.7.9", only: [:dev, :test], runtime: false}, {:dialyxir, "~> 1.2", only: [:dev], runtime: false}, {:dotenv_parser, "~> 2.0"}, {:earmark, "~> 1.4"}, @@ -55,7 +55,7 @@ defmodule EpochtalkServer.MixProject do {:hackney, "~> 1.9"}, {:hammer, "~> 6.2"}, {:hammer_backend_redis, "~> 6.1"}, - {:html_entities, "~> 0.5.2", only: [:dev]}, + {:html_entities, "~> 0.5.2", only: [:dev, :test]}, {:html_sanitize_ex, "~> 1.4"}, {:iteraptor, git: "https://github.com/epochtalk/elixir-iteraptor.git", tag: "1.13.1"}, {:jason, "~> 1.4.0"}, @@ -65,6 +65,7 @@ defmodule EpochtalkServer.MixProject do {:phoenix_ecto, "~> 4.4"}, {:phoenix_html, "~> 3.0"}, {:plug_cowboy, "~> 2.5"}, + {:poolboy, "~> 1.5.1"}, {:porcelain, "~> 2.0"}, {:poison, "~> 3.0"}, {:postgrex, "~> 0.17.1"}, diff --git a/mix.lock b/mix.lock index fb3eb478..78ba78d6 100644 --- a/mix.lock +++ b/mix.lock @@ -10,7 +10,7 @@ "cowboy": {:hex, :cowboy, "2.12.0", "f276d521a1ff88b2b9b4c54d0e753da6c66dd7be6c9fca3d9418b561828a3731", [:make, :rebar3], [{:cowlib, "2.13.0", [hex: :cowlib, repo: "hexpm", optional: false]}, {:ranch, "1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "8a7abe6d183372ceb21caa2709bec928ab2b72e18a3911aa1771639bef82651e"}, "cowboy_telemetry": {:hex, :cowboy_telemetry, "0.4.0", "f239f68b588efa7707abce16a84d0d2acf3a0f50571f8bb7f56a15865aae820c", [:rebar3], [{:cowboy, "~> 2.7", [hex: :cowboy, repo: "hexpm", optional: false]}, {:telemetry, "~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7d98bac1ee4565d31b62d59f8823dfd8356a169e7fcbb83831b8a5397404c9de"}, "cowlib": {:hex, :cowlib, "2.13.0", "db8f7505d8332d98ef50a3ef34b34c1afddec7506e4ee4dd4a3a266285d282ca", [:make, :rebar3], [], "hexpm", "e1e1284dc3fc030a64b1ad0d8382ae7e99da46c3246b815318a4b848873800a4"}, - "credo": {:hex, :credo, "1.7.6", "b8f14011a5443f2839b04def0b252300842ce7388f3af177157c86da18dfbeea", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "146f347fb9f8cbc5f7e39e3f22f70acbef51d441baa6d10169dd604bfbc55296"}, + "credo": {:hex, :credo, "1.7.9", "07bb31907746ae2b5e569197c9e16c0d75c8578a22f01bee63f212047efb2647", [:mix], [{:bunt, "~> 0.2.1 or ~> 1.0", [hex: :bunt, repo: "hexpm", optional: false]}, {:file_system, "~> 0.2 or ~> 1.0", [hex: :file_system, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}], "hexpm", "f87c11c34ba579f7c5044f02b2a807e1ed2fa5fdbb24dc7eb4ad59c1904887f3"}, "db_connection": {:hex, :db_connection, "2.6.0", "77d835c472b5b67fc4f29556dee74bf511bbafecdcaf98c27d27fa5918152086", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c2f992d15725e721ec7fbc1189d4ecdb8afef76648c746a8e1cad35e3b8a35f3"}, "decimal": {:hex, :decimal, "2.1.1", "5611dca5d4b2c3dd497dec8f68751f1f1a54755e8ed2a966c2633cf885973ad6", [:mix], [], "hexpm", "53cfe5f497ed0e7771ae1a475575603d77425099ba5faef9394932b35020ffcc"}, "dialyxir": {:hex, :dialyxir, "1.4.3", "edd0124f358f0b9e95bfe53a9fcf806d615d8f838e2202a9f430d59566b6b53b", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "bf2cfb75cd5c5006bec30141b131663299c661a864ec7fbbc72dfa557487a986"}, @@ -26,7 +26,7 @@ "ex_doc": {:hex, :ex_doc, "0.29.4", "6257ecbb20c7396b1fe5accd55b7b0d23f44b6aa18017b415cb4c2b91d997729", [:mix], [{:earmark_parser, "~> 1.4.31", [hex: :earmark_parser, repo: "hexpm", optional: false]}, {:makeup_elixir, "~> 0.14", [hex: :makeup_elixir, repo: "hexpm", optional: false]}, {:makeup_erlang, "~> 0.1", [hex: :makeup_erlang, repo: "hexpm", optional: false]}], "hexpm", "2c6699a737ae46cb61e4ed012af931b57b699643b24dabe2400a8168414bc4f5"}, "ex_machina": {:hex, :ex_machina, "2.7.0", "b792cc3127fd0680fecdb6299235b4727a4944a09ff0fa904cc639272cd92dc7", [:mix], [{:ecto, "~> 2.2 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}, {:ecto_sql, "~> 3.0", [hex: :ecto_sql, repo: "hexpm", optional: true]}], "hexpm", "419aa7a39bde11894c87a615c4ecaa52d8f107bbdd81d810465186f783245bf8"}, "ex_utils": {:hex, :ex_utils, "0.1.7", "2c133e0bcdc49a858cf8dacf893308ebc05bc5fba501dc3d2935e65365ec0bf3", [:mix], [], "hexpm", "66d4fe75285948f2d1e69c2a5ddd651c398c813574f8d36a9eef11dc20356ef6"}, - "file_system": {:hex, :file_system, "1.0.0", "b689cc7dcee665f774de94b5a832e578bd7963c8e637ef940cd44327db7de2cd", [:mix], [], "hexpm", "6752092d66aec5a10e662aefeed8ddb9531d79db0bc145bb8c40325ca1d8536d"}, + "file_system": {:hex, :file_system, "1.0.1", "79e8ceaddb0416f8b8cd02a0127bdbababe7bf4a23d2a395b983c1f8b3f73edd", [:mix], [], "hexpm", "4414d1f38863ddf9120720cd976fce5bdde8e91d8283353f0e31850fa89feb9e"}, "finch": {:hex, :finch, "0.18.0", "944ac7d34d0bd2ac8998f79f7a811b21d87d911e77a786bc5810adb75632ada4", [:mix], [{:castore, "~> 0.1 or ~> 1.0", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4 or ~> 1.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6 or ~> 1.0", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "69f5045b042e531e53edc2574f15e25e735b522c37e2ddb766e15b979e03aa65"}, "gen_smtp": {:hex, :gen_smtp, "1.2.0", "9cfc75c72a8821588b9b9fe947ae5ab2aed95a052b81237e0928633a13276fd3", [:rebar3], [{:ranch, ">= 1.8.0", [hex: :ranch, repo: "hexpm", optional: false]}], "hexpm", "5ee0375680bca8f20c4d85f58c2894441443a743355430ff33a783fe03296779"}, "guardian": {:hex, :guardian, "2.3.2", "78003504b987f2b189d76ccf9496ceaa6a454bb2763627702233f31eb7212881", [:mix], [{:jose, "~> 1.8", [hex: :jose, repo: "hexpm", optional: false]}, {:plug, "~> 1.3.3 or ~> 1.4", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "b189ff38cd46a22a8a824866a6867ca8722942347f13c33f7d23126af8821b52"}, @@ -41,7 +41,7 @@ "html_sanitize_ex": {:hex, :html_sanitize_ex, "1.4.3", "67b3d9fa8691b727317e0cc96b9b3093be00ee45419ffb221cdeee88e75d1360", [:mix], [{:mochiweb, "~> 2.15 or ~> 3.1", [hex: :mochiweb, repo: "hexpm", optional: false]}], "hexpm", "87748d3c4afe949c7c6eb7150c958c2bcba43fc5b2a02686af30e636b74bccb7"}, "idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~> 0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"}, "iteraptor": {:git, "https://github.com/epochtalk/elixir-iteraptor.git", "d8d1c386c38e06bdfcf60c9ce1abf8e49161cab4", [tag: "1.13.1"]}, - "jason": {:hex, :jason, "1.4.1", "af1504e35f629ddcdd6addb3513c3853991f694921b1b9368b0bd32beb9f1b63", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fbb01ecdfd565b56261302f7e1fcc27c4fb8f32d56eab74db621fc154604a7a1"}, + "jason": {:hex, :jason, "1.4.4", "b9226785a9aa77b6857ca22832cffa5d5011a667207eb2a0ad56adb5db443b8a", [:mix], [{:decimal, "~> 1.0 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "c5eb0cab91f094599f94d55bc63409236a8ec69a21a67814529e8d5f6cc90b3b"}, "jose": {:hex, :jose, "1.11.10", "a903f5227417bd2a08c8a00a0cbcc458118be84480955e8d251297a425723f83", [:mix, :rebar3], [], "hexpm", "0d6cd36ff8ba174db29148fc112b5842186b68a90ce9fc2b3ec3afe76593e614"}, "makeup": {:hex, :makeup, "1.1.2", "9ba8837913bdf757787e71c1581c21f9d2455f4dd04cfca785c70bbfff1a76a3", [:mix], [{:nimble_parsec, "~> 1.2.2 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "cce1566b81fbcbd21eca8ffe808f33b221f9eee2cbc7a1706fc3da9ff18e6cac"}, "makeup_elixir": {:hex, :makeup_elixir, "0.16.2", "627e84b8e8bf22e60a2579dad15067c755531fea049ae26ef1020cad58fe9578", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}, {:nimble_parsec, "~> 1.2.3 or ~> 1.3", [hex: :nimble_parsec, repo: "hexpm", optional: false]}], "hexpm", "41193978704763f6bbe6cc2758b84909e62984c7752b3784bd3c218bb341706b"}, diff --git a/parsing_extra.php b/parsing_extra.php index aa17088a..2491ab44 100644 --- a/parsing_extra.php +++ b/parsing_extra.php @@ -15,6 +15,10 @@ function setReasonableValues() $context['browser']['is_ie5'] = false; $context['browser']['is_ie5.5'] = false; + // Fix for portuguese characters (test: http://localhost:8000/threads/5515847) + $context['utf8'] = true; + $context['server']['complex_preg_chars'] = true; + $txt['lang_character_set'] = 'ISO-8859-1'; $txt['smf238'] = 'Code'; $txt['smf240'] = 'Quote'; diff --git a/test/epochtalk_server_web/controllers/post_test.exs b/test/epochtalk_server_web/controllers/post_test.exs index 77ee765b..7096fc93 100644 --- a/test/epochtalk_server_web/controllers/post_test.exs +++ b/test/epochtalk_server_web/controllers/post_test.exs @@ -42,7 +42,7 @@ defmodule Test.EpochtalkServerWeb.Controllers.Post do fn -> post(conn, Routes.post_path(conn, :preview), %{ "body" => - for(_ <- 1..10_001, into: "", do: <>) + for(_ <- 1..10_001, into: "", do: <>) }) end end diff --git a/test/epochtalk_server_web/controllers/user_test.exs b/test/epochtalk_server_web/controllers/user_test.exs index 2319200d..52d9733e 100644 --- a/test/epochtalk_server_web/controllers/user_test.exs +++ b/test/epochtalk_server_web/controllers/user_test.exs @@ -56,16 +56,16 @@ defmodule Test.EpochtalkServerWeb.Controllers.User do end end - @tag :banned describe "unban/1" do + @tag :banned test "unbans banned user", %{users: %{user: user}} do {:ok, unbanned_user_changeset} = Ban.unban(user) assert unbanned_user_changeset.ban_info == nil end end - @tag :malicious describe "handle_malicious_user/2" do + @tag :malicious test "populates ban_info and malicious_score if user is malicious", %{ users: %{user: user}, malicious_user_changeset: malicious_user_changeset @@ -314,10 +314,10 @@ defmodule Test.EpochtalkServerWeb.Controllers.User do response = conn |> delete(Routes.user_path(conn, :logout)) - |> json_response(400) + |> json_response(401) - assert response["error"] == "Bad Request" - assert response["message"] == "Not logged in" + assert response["error"] == "Unauthorized" + assert response["message"] == "No resource found" end end