diff --git a/.dialyzer_ignore.exs b/.dialyzer_ignore.exs index 9ae13a0f6..dbb9c8904 100644 --- a/.dialyzer_ignore.exs +++ b/.dialyzer_ignore.exs @@ -11,7 +11,6 @@ {"lib/teiserver/account/reports/population_report.ex", :unused_fun}, {"lib/teiserver/account/reports/time_compare_report.ex", :invalid_contract}, {"lib/teiserver/account/reports/time_compare_report.ex", :call}, - {"lib/teiserver/account/reports/tournament_report.ex", :invalid_contract}, {"lib/teiserver/account/servers/party_server.ex", :no_return}, {"lib/teiserver/account/servers/party_server.ex", :call}, {"lib/teiserver/battle.ex", :call}, @@ -29,7 +28,6 @@ {"lib/teiserver/battle/tasks/post_match_process_task.ex", :call}, {"lib/teiserver/battle/tasks/post_match_process_task.ex", :unused_fun}, {"lib/teiserver/bridge/chat_commands.ex", :call}, - {"lib/teiserver/bridge/commands/findreports_command.ex", :pattern_match}, {"lib/teiserver/bridge/discord_bridge_bot.ex", :guard_fail}, {"lib/teiserver/bridge/discord_bridge_bot.ex", :no_return}, {"lib/teiserver/bridge/discord_bridge_bot.ex", :call}, @@ -97,8 +95,6 @@ {"lib/teiserver/mix_tasks/fake_data.ex", :no_return}, {"lib/teiserver/mix_tasks/fake_data.ex", :unused_fun}, {"lib/teiserver/moderation.ex", :unknown_type}, - {"lib/teiserver/moderation.ex", :invalid_contract}, - {"lib/teiserver/moderation/libs/report_group_lib.ex", :call}, {"lib/teiserver/moderation/libs/test_lib.ex", :invalid_contract}, {"lib/teiserver/protocols/spring/spring.ex", :unknown_type}, {"lib/teiserver/protocols/spring/spring_battle_in.ex", :call}, diff --git a/documents/planned_designs/clustering.md b/documents/planned_designs/clustering.md index 7bfe39ba1..4f38cc975 100644 --- a/documents/planned_designs/clustering.md +++ b/documents/planned_designs/clustering.md @@ -93,7 +93,6 @@ Note: This ties in with the "Per node process" item. - List of per ID processes (1 per id in entire cluster): - ConsulServer - LobbyThrottleServer - - AccoladeChatServer - QueueServer - Per cluster processes that might need to be changed to use pooled resources (e.g. worker pool): - CoordinatorServer diff --git a/documents/planned_designs/tournaments.md b/documents/planned_designs/tournaments.md deleted file mode 100644 index 1732257bb..000000000 --- a/documents/planned_designs/tournaments.md +++ /dev/null @@ -1,133 +0,0 @@ -### Ideas - Core -- Bracket designs -- Tourney types (bracket, round robin etc) -- Ability to connect one tourney to another (e.g. TI group stages -> main event) -- Signup process, might need to be linked to discord -- Track bat -- Admin functionality, maybe something like they temporarily have "friends list" like interactions with all players? -- Challonge API https://api.challonge.com/v1 -- Don't allow spectators in tourney games except mods/organisers (as an option) - -### Ideas - Extra -- Predicitons -- Ability for people to sign up as standins should people drop out -- Ability to jump to spectate a player's game -- Automatically pull people into their game if ready -- Alerts of when a game should be starting and warnings to admins regarding late starts -- Online checker for specific players - -### Extensive ideas from Dubhdara -- Signup within client -- Signup within discord -- Signup on website -- Tourney rules page (specific to the tourney) -- Support for single elimination -- Match scheduling (automatic and manual) -- Score reporting (Challonge API?) -- Async and Synchronous support in terms of player (un)availability -- Tourney page should have indicators of maps and settings for each game played and to-play -- "Flake score" for players to indicate those that signup for and leave tourneys - -### API suggestion from Dubhdara -``` -data Competition = Tournament | League # Interface - teams :: [[Player]] - matches :: [Match] - startDate :: UTCTime - rules :: [String] -} - -data Tournament = inherits Competition { - startTime :: UTCTime - bracket = Tree Matches - activePlayers :: Players -} - -data League = League inherits Competition { - roundDurationDays :: Int - scoreBoard :: [(Player, Int)] -} - -data Player | Player { - id :: String - scores :: Map(Tournament, Int) - availibleTimes :: [(UTCTime, UTCTime)] -} - -data Match = Match { - startTime :: UTCTime - isScheduled :: Bool - players :: [Player] - map :: String -} - -CompetetionRunner { - getNextMatches :: ([Player, Matches]) -> [Matches] - displayMatches :: [Matches] - -TournamentRunner { - randomizeBrackets :: Tree Matches - setBrackets :: Tree Matches -> Tree Matches - displayBrackets :: [Matches] - setActivePlayers :: [Player] -> () -} - -LeagueRunner { - scheduleNextMatch ([Match], [Player]) -> Match - displayScores ([Player] -> Map(Player, Int) - advanceRound :: [Player] -> [Player, Match] -} - -PlayerAction { - forfitMatch :: Match -> () - reportScore :: (Int, Int, [Player]) -> () # Team1Score, Team2Score, Winners - signUpForCompetition :: Competition -> () - } - -AdminAction { - startCompetition :: Tournament -> () - sendAnnoucement :: String -> () -} -``` - -### Schema -Tournament -- name -- description -- rules {:array, String()} -- type (1v1, 3v3, 5v5, FFA etc) -- format (swiss, round robin etc) -- start date -- discord_event_id -- challonge_id -- configs (e.g. allow spectators, welcome-messages) -- brackets (maps per bracket etc) -- stream_urls {:array, String()} - -TournamentMember # This tracks membership and role in a tourney -- userid (discord_id is tracked against user) -- role (organiser, caster, player, coach etc) -- noshow (boolean) - -TournamentTeam -- name -- tournament_id -- position (int) -- eliminated (boolean) - -TournamentTeamMember # This tracks membership of a team -- userid (discord_id is tracked against user) -- team_id - -TournamentSeries -- tournament_id - -TournamentGame -- series_id -- match_id -- tournament_id - -TournamentPrediction -- userid -- tournament_id -- prediction_data (map) diff --git a/lib/teiserver.ex b/lib/teiserver.ex index a85c7460c..2f39b0e0f 100644 --- a/lib/teiserver.ex +++ b/lib/teiserver.ex @@ -1,6 +1,5 @@ defmodule Teiserver do @moduledoc false - alias Teiserver.Account.AccoladeLib alias Teiserver.Admin.DeleteUserTask alias Teiserver.Data.Types, as: T alias Teiserver.Helpers.CacheHelper @@ -27,12 +26,6 @@ defmodule Teiserver do :code.load_file(module) end - @spec accolade_status :: nil | :ok - def accolade_status do - Application.put_env(:elixir, :ansi_enabled, true) - AccoladeLib.live_debug() - end - @spec manually_delete_user(T.userid()) :: :ok def manually_delete_user(id) do Application.put_env(:elixir, :ansi_enabled, true) diff --git a/lib/teiserver/account/accolades/accolade_bot_server.ex b/lib/teiserver/account/accolades/accolade_bot_server.ex deleted file mode 100644 index bc95562b1..000000000 --- a/lib/teiserver/account/accolades/accolade_bot_server.ex +++ /dev/null @@ -1,247 +0,0 @@ -defmodule Teiserver.Account.AccoladeBotServer do - @moduledoc """ - The accolade server is the interface point for the Accolade system. - """ - - alias Phoenix.PubSub - alias Teiserver.Account - alias Teiserver.Account.AccoladeLib - alias Teiserver.Account.Auth - alias Teiserver.Battle - alias Teiserver.CacheUser - alias Teiserver.Config - alias Teiserver.Coordinator.CoordinatorCommands - alias Teiserver.Coordinator.Parser - alias Teiserver.Room - use GenServer - require Logger - - @spec max_miss_count :: float - def max_miss_count do - AccoladeLib.miss_count_limit() * 1.5 - end - - @spec start_link(list()) :: :ignore | {:error, any} | {:ok, pid} - def start_link(opts) do - GenServer.start_link(__MODULE__, opts[:data], []) - end - - @impl GenServer - def handle_call(:client_state, _from, state) do - {:reply, state.client, state} - end - - @impl GenServer - def handle_cast({:update_client, new_client}, state) do - {:noreply, %{state | client: new_client}} - end - - def handle_cast({:merge_client, partial_client}, state) do - {:noreply, %{state | client: Map.merge(state.client, partial_client)}} - end - - @impl GenServer - def handle_info(:begin, _state) do - Logger.debug("Starting up Accolade server") - account = get_accolade_account() - Teiserver.cache_put(:application_metadata_cache, "teiserver_accolade_userid", account.id) - - {user, client} = - case CacheUser.internal_client_login(account.id) do - {:ok, user, client} -> {user, client} - :error -> raise "No accolade user found" - end - - state = %{ - ip: "127.0.0.1", - userid: user.id, - username: user.name, - client: client - } - - ~w(main accolades) - |> Enum.each(fn room_name -> - Room.get_or_make_room(room_name, user.id) - Room.add_user_to_room(user.id, room_name) - :ok = PubSub.subscribe(Teiserver.PubSub, "room:#{room_name}") - end) - - :ok = PubSub.subscribe(Teiserver.PubSub, "legacy_user_updates:#{user.id}") - - # We only subscribe to this if we're not in test, if - # we are it'll generate a bunch of SQL errors - # without actually breaking anything - if not Application.get_env(:teiserver, Teiserver)[:test_mode] do - :ok = PubSub.subscribe(Teiserver.PubSub, "global_match_updates") - end - - {:noreply, state} - end - - # Match ending - def handle_info( - %{channel: "global_match_updates", event: :match_completed, match_id: match_id}, - state - ) do - case Battle.get_match(match_id) do - nil -> - nil - - match -> - duration = Timex.diff(match.finished, match.started, :second) - - if duration > 600 do - if Config.get_site_config_cache("teiserver.Enable accolades") do - post_match_messages(match) - end - else - :ok - end - end - - {:noreply, state} - end - - def handle_info(%{channel: "global_match_updates"}, state) do - {:noreply, state} - end - - # Direct/Room messaging - def handle_info({:add_user_to_room, _userid, _room_name}, state), do: {:noreply, state} - def handle_info({:remove_user_from_room, _userid, _room_name}, state), do: {:noreply, state} - - def handle_info({:new_message, _userid, _room_name, _message}, state), do: {:noreply, state} - def handle_info({:new_message_ex, _userid, _room_name, _message}, state), do: {:noreply, state} - - def handle_info({:direct_message, from_id, parts}, state) when is_list(parts) do - new_state = - parts - |> Enum.reduce(state, fn part, acc_state -> - {_reply, new_state} = handle_info({:direct_message, from_id, part}, acc_state) - new_state - end) - - {:noreply, new_state} - end - - def handle_info({:direct_message, sender_id, "$" <> command}, state) do - cmd = Parser.parse_command(sender_id, "$#{command}") - new_state = CoordinatorCommands.handle_command(cmd, state) - - {:noreply, new_state} - end - - def handle_info({:direct_message, userid, message}, state) do - if not Auth.is_bot?(userid) do - case AccoladeLib.cast_accolade_chat(userid, {:user_message, message}) do - nil -> - CacheUser.send_direct_message( - state.userid, - userid, - "I'm not currently awaiting feedback for a player" - ) - - _result -> - :ok - end - end - - {:noreply, state} - end - - def handle_info({:new_accolade, userid}, state) do - CacheUser.send_direct_message( - state.userid, - userid, - "You have been awarded a new accolade send $whoami to myself to see your collection." - ) - - {:noreply, state} - end - - # Client inout - def handle_info(%{channel: "client_inout", event: :login, userid: userid}, state) do - :timer.send_after(500, {:do_client_inout, :login, userid}) - {:noreply, state} - end - - # Catchall handle_info - def handle_info(msg, state) do - Logger.error( - "Accolade server handle_info error. No handler for msg of #{Kernel.inspect(msg)}" - ) - - {:noreply, state} - end - - @spec get_accolade_account() :: Teiserver.Account.User.t() - def get_accolade_account do - user = - Account.get_user(nil, - search: [ - email: "accolades_bot@teiserver.local" - ] - ) - - case user do - nil -> - # Make account - {:ok, account} = - Account.script_create_user(%{ - name: "AccoladesBot", - email: "accolades_bot@teiserver.local", - icon: "fa-solid #{AccoladeLib.icon()}" |> String.replace(" far ", " "), - colour: "#0066AA", - password: Account.make_bot_password(), - roles: ["Bot", "Verified"], - data: %{ - bot: true, - moderator: false, - lobby_client: "Teiserver Internal Process" - } - }) - - Account.update_user_stat(account.id, %{ - country_override: Application.get_env(:teiserver, Teiserver)[:server_flag] - }) - - CacheUser.recache_user(account.id) - account - - account -> - account - end - end - - defp post_match_messages(%{id: match_id} = _match) do - Logger.info("AccoladeBotServer post match messages for #{match_id}") - - # Get a list of all the players, then check if there are possible ratings for them - memberships = Battle.list_match_memberships(search: [match_id: match_id]) - - memberships - |> Enum.each(fn %{user_id: userid} -> - case AccoladeLib.get_possible_ratings(userid, memberships) do - [] -> - :ok - - possibles -> - chosen = Enum.random(possibles) - AccoladeLib.start_accolade_process(userid, chosen, match_id) - end - end) - end - - @impl GenServer - @spec init(map()) :: {:ok, map()} - def init(_opts) do - Horde.Registry.register( - Teiserver.AccoladesRegistry, - "AccoladeBotServer", - :accolade_bot - ) - - send(self(), :begin) - {:ok, %{}} - end -end diff --git a/lib/teiserver/account/accolades/accolade_chat_server.ex b/lib/teiserver/account/accolades/accolade_chat_server.ex deleted file mode 100644 index 39bbf2b2e..000000000 --- a/lib/teiserver/account/accolades/accolade_chat_server.ex +++ /dev/null @@ -1,229 +0,0 @@ -defmodule Teiserver.Account.AccoladeChatServer do - @moduledoc """ - Each chat server is for a specific user, when the chat server has done it's job it self-terminates. - """ - - alias Teiserver.Account - alias Teiserver.Account.AccoladeBotServer - alias Teiserver.Account.AccoladeLib - alias Teiserver.CacheUser - alias Teiserver.Config - alias Teiserver.Data.Types, as: T - - use GenServer - - @line_break "-------------------------------------------------" - @chat_timeout 600_000 - - def start_link(opts) do - GenServer.start_link(__MODULE__, opts[:data], []) - end - - @spec empty_state(T.userid(), T.userid(), T.lobby_id()) :: map() - def empty_state(userid, recipient_id, match_id) do - badge_types = AccoladeLib.get_badge_types() - - %{ - bot_id: AccoladeLib.get_accolade_bot_userid(), - badge_types: badge_types, - userid: userid, - user: Account.get_user_by_id(userid), - recipient_id: recipient_id, - recipient: Account.get_user_by_id(recipient_id), - match_id: match_id, - stage: :not_started - } - end - - def handle_call(:ping, _from, state) do - {:reply, :ok, state} - end - - # Doesn't do anything at this stage - def handle_info(:startup, state) do - new_state = do_tick(state) - :timer.send_after(@chat_timeout, :self_terminate) - {:noreply, new_state} - end - - # Handling user messages - def handle_info({:user_message, message}, %{stage: :awaiting_choice} = state) do - integer_choice = - case message |> String.trim() |> Integer.parse() do - :error -> :error - {v, _rest} -> v - end - - new_state = - cond do - String.downcase(message) == "n" -> - Account.create_accolade(%{ - recipient_id: state.recipient_id, - giver_id: state.userid, - match_id: state.match_id, - inserted_at: Timex.now() - }) - - increment_miss_count(state.userid, 3) - - CacheUser.send_direct_message( - state.bot_id, - state.userid, - "Thank you for your feedback, no Accolade will be bestowed." - ) - - send(self(), :terminate) - %{state | stage: :completed} - - integer_choice == 0 -> - increment_miss_count(state.userid, 3) - - CacheUser.send_direct_message( - state.bot_id, - state.userid, - "Thank you for your feedback, no Accolade will be bestowed." - ) - - send(self(), :terminate) - %{state | stage: :completed} - - integer_choice != :error -> - badge_type = - state.badge_types - |> Enum.filter(fn {i, _badge_type} -> i == integer_choice end) - - case badge_type do - [] -> - CacheUser.send_direct_message( - state.bot_id, - state.userid, - "None of the listed Accolades match that option" - ) - - state - - [{_index, selected_type}] -> - Account.create_accolade(%{ - recipient_id: state.recipient_id, - giver_id: state.userid, - badge_type_id: selected_type.id, - match_id: state.match_id, - inserted_at: Timex.now() - }) - - if Config.get_site_config_cache("teiserver.Inform of new accolades") do - bot_pid = AccoladeLib.get_accolade_bot_pid() - :timer.send_after(30_000, bot_pid, {:new_accolade, state.recipient_id}) - end - - decrement_miss_count(state.userid, 5) - - CacheUser.send_direct_message( - state.bot_id, - state.userid, - "Thank you for your feedback, this Accolade will be bestowed." - ) - - send(self(), :terminate) - %{state | stage: :completed} - end - - :error -> - CacheUser.send_direct_message( - state.bot_id, - state.userid, - "I'm sorry but I can't pick an Accolade based on that value" - ) - - state - end - - {:noreply, new_state} - end - - def handle_info(:self_terminate, state) do - increment_miss_count(state.userid, 10) - - send(self(), :terminate) - {:noreply, state} - end - - def handle_info(:terminate, state) do - DynamicSupervisor.terminate_child(Teiserver.Account.AccoladeSupervisor, self()) - {:stop, :normal, %{state | userid: nil}} - end - - def terminate(_reason, _state) do - :ok - end - - @spec do_tick(map()) :: map() - defp do_tick(state) do - case state.stage do - :not_started -> send_initial_message(state) - end - end - - defp decrement_miss_count(userid, amount) do - stats = Account.get_user_stat_data(userid) - accolade_miss_count = Map.get(stats, "accolade_miss_count", 0) - - Account.update_user_stat(userid, %{ - "accolade_miss_count" => max(accolade_miss_count - amount, 0) - }) - end - - defp increment_miss_count(userid, amount) do - stats = Account.get_user_stat_data(userid) - accolade_miss_count = Map.get(stats, "accolade_miss_count", 0) - - Account.update_user_stat(userid, %{ - "accolade_miss_count" => - min(max(accolade_miss_count + amount, 0), AccoladeBotServer.max_miss_count()) - }) - end - - @spec send_initial_message(map()) :: map() - defp send_initial_message(state) do - badge_lines = - state.badge_types - |> Enum.map(fn {i, bt} -> "#{i} - #{bt.name}, #{bt.description}" end) - - CacheUser.send_direct_message( - state.bot_id, - state.userid, - [ - @line_break, - "You have an opportunity to leave feedback on one of the players in your last game. We have selected #{state.recipient.name}", - "Which of the following accolades do you feel they most deserve (if any)?", - "N - No accolade for this player at all", - "0 - No accolade this time, ask again later" - ] ++ - badge_lines ++ - [ - ".", - "Reply to this message with the number corresponding to the Accolade you feel is most appropriate for this player for this match." - ] - ) - - %{state | stage: :awaiting_choice} - end - - @spec init(map()) :: {:ok, map()} - def init(opts) do - userid = opts[:userid] - recipient_id = opts[:recipient_id] - match_id = opts[:match_id] - - # Update the queue pids cache to point to this process - Horde.Registry.register( - Teiserver.AccoladesRegistry, - "AccoladeChatServer:#{userid}", - userid - ) - - # :timer.send_interval(10_000, :tick) - send(self(), :startup) - {:ok, empty_state(userid, recipient_id, match_id)} - end -end diff --git a/lib/teiserver/account/libs/accolade_lib.ex b/lib/teiserver/account/libs/accolade_lib.ex index 5f81a0732..e622e84d0 100644 --- a/lib/teiserver/account/libs/accolade_lib.ex +++ b/lib/teiserver/account/libs/accolade_lib.ex @@ -4,8 +4,6 @@ defmodule Teiserver.Account.AccoladeLib do alias Ecto.Adapters.SQL alias Teiserver.Account alias Teiserver.Account.Accolade - alias Teiserver.Account.AccoladeBotServer - alias Teiserver.Account.AccoladeChatServer alias Teiserver.CacheUser alias Teiserver.Data.Types, as: T use TeiserverWeb, :library @@ -195,88 +193,6 @@ defmodule Teiserver.Account.AccoladeLib do preload: [giver: givers] end - @spec do_start() :: :ok - defp do_start do - # Start the supervisor server - {:ok, _accolade_server_pid} = - DynamicSupervisor.start_child(Teiserver.Account.AccoladeSupervisor, { - AccoladeBotServer, - name: Teiserver.Account.AccoladeBotServer, data: %{} - }) - - :ok - end - - @spec start_accolade_server() :: :ok | {:failure, String.t()} - def start_accolade_server do - if is_nil(get_accolade_bot_userid()) do - do_start() - else - {:failure, "Already started"} - end - end - - @spec cast_accolade_bot(any) :: any - def cast_accolade_bot(msg) do - case get_accolade_bot_pid() do - nil -> nil - pid -> send(pid, msg) - end - end - - @spec call_accolade_bot(any) :: any - def call_accolade_bot(msg) do - case get_accolade_bot_pid() do - nil -> - nil - - pid -> - try do - GenServer.call(pid, msg) - - # If the process has somehow died, we just return nil - catch - :exit, _reason -> - nil - end - end - end - - @spec get_accolade_bot_userid() :: T.userid() | nil - def get_accolade_bot_userid do - Teiserver.cache_get(:application_metadata_cache, "teiserver_accolade_userid") - end - - @spec get_accolade_bot_pid() :: pid() | nil - def get_accolade_bot_pid do - case Horde.Registry.lookup(Teiserver.AccoladesRegistry, "AccoladeBotServer") do - [{pid, _value}] -> - pid - - _other -> - nil - end - end - - @spec get_accolade_chat_pid(T.userid()) :: pid() | nil - def get_accolade_chat_pid(userid) do - case Horde.Registry.lookup(Teiserver.AccoladesRegistry, "AccoladeChatServer:#{userid}") do - [{pid, _value}] -> - pid - - _other -> - nil - end - end - - @spec cast_accolade_chat(T.userid(), any) :: any - def cast_accolade_chat(userid, msg) do - case get_accolade_chat_pid(userid) do - nil -> nil - pid -> send(pid, msg) - end - end - @spec get_possible_ratings(T.userid(), [map()]) :: any def get_possible_ratings(userid, memberships) do their_membership = Enum.filter(memberships, fn m -> m.user_id == userid end) |> hd() @@ -317,33 +233,6 @@ defmodule Teiserver.Account.AccoladeLib do end end - @spec start_chat_server(T.userid(), T.userid(), T.lobby_id()) :: pid() - def start_chat_server(userid, recipient_id, match_id) do - {:ok, chat_server_pid} = - DynamicSupervisor.start_child(Teiserver.Account.AccoladeSupervisor, { - AccoladeChatServer, - name: "accolade_chat_#{userid}", - data: %{ - userid: userid, - recipient_id: recipient_id, - match_id: match_id - } - }) - - chat_server_pid - end - - @spec start_accolade_process(T.userid(), T.userid(), T.lobby_id()) :: :ok | :existing - def start_accolade_process(userid, recipient_id, match_id) do - case get_accolade_chat_pid(userid) do - nil -> - start_chat_server(userid, recipient_id, match_id) - - _pid -> - :existing - end - end - @spec get_badge_types() :: [{non_neg_integer(), map()}] def get_badge_types do Teiserver.cache_get_or_store(:application_temp_cache, "accolade_badges", fn -> @@ -388,46 +277,6 @@ order by name;" |> Map.new(fn {k, v} -> {k, Enum.count(v)} end) end - @spec live_debug :: nil | :ok - def live_debug do - case get_accolade_bot_pid() do - nil -> - Logger.error("Error, no accolade bot pid") - - pid -> - state = :sys.get_state(pid) - children = DynamicSupervisor.which_children(Teiserver.Account.AccoladeSupervisor) - child_count = Enum.count(children) - 1 - - Logger.info("Accolade bot found, state is:") - Logger.info("#{Kernel.inspect(state)}") - Logger.info("Accolade chat count: #{child_count}") - - if Enum.count(children) > 1 do - Logger.info("Pinging all chat servers...") - - pings = - children - |> ParallelStream.filter(fn {_id, _child, _type, [module]} -> - module == Teiserver.Account.AccoladeChatServer - end) - |> ParallelStream.map(fn {_id, pid, _type, _modules} -> - case GenServer.call(pid, :ping, 5000) do - :ok -> :ok - _other -> :not_okay - end - end) - |> Enum.filter(fn p -> p == :ok end) - - rate = (Enum.count(pings) / child_count * 100) |> round() - - Logger.info( - "Out of #{child_count} children, #{Enum.count(pings)} respond to ping (#{rate}%)" - ) - end - end - end - def get_number_of_gifted_accolades(user_id, window_days) do query = """ select count(*) from teiserver_account_accolades taa diff --git a/lib/teiserver/account/libs/role_lib.ex b/lib/teiserver/account/libs/role_lib.ex index 83988fff6..91f5c6e94 100644 --- a/lib/teiserver/account/libs/role_lib.ex +++ b/lib/teiserver/account/libs/role_lib.ex @@ -97,12 +97,11 @@ defmodule Teiserver.Account.RoleLib do # Privileged %{name: "VIP", colour: "#AA8833", icon: "fa-solid fa-sparkles", contains: ~w()}, %{name: "Streamer", colour: "#660066", icon: "fa-brands fa-twitch", contains: ~w()}, - %{name: "Tournament", colour: "#0000AA", icon: "fa-solid fa-trophy", contains: ~w()}, %{ name: "Caster", colour: "#660066", icon: "fa-solid fa-microphone-lines", - contains: ~w(Streamer Tournament), + contains: ~w(Streamer), badge: true }, %{name: "Donor", colour: "#0066AA", icon: "fa-solid fa-euro", contains: ~w(), badge: true}, @@ -277,7 +276,7 @@ defmodule Teiserver.Account.RoleLib do @spec privileged_roles :: [String.t()] def privileged_roles do - ~w(Bot VIP Caster Donor Tournament) + ~w(Bot VIP Caster Donor) end @spec property_roles :: [String.t()] diff --git a/lib/teiserver/account/queries/user_queries.ex b/lib/teiserver/account/queries/user_queries.ex index 8bfd64807..1a38a0052 100644 --- a/lib/teiserver/account/queries/user_queries.ex +++ b/lib/teiserver/account/queries/user_queries.ex @@ -354,16 +354,6 @@ defmodule Teiserver.Account.UserQueries do where: fragment("not ? -> ? @> ?", users.data, "roles", "\"Caster\"") end - def _where(query, :tournament_player, "Player") do - from users in query, - where: fragment("? -> ? @> ?", users.data, "roles", "\"Tournament player\"") - end - - def _where(query, :tournament_player, "Normal") do - from users in query, - where: fragment("not ? -> ? @> ?", users.data, "roles", "\"Tournament player\"") - end - def _where(query, :vip, "VIP") do from users in query, where: fragment("? -> ? @> ?", users.data, "roles", "\"VIP\"") diff --git a/lib/teiserver/account/reports/tournament_report.ex b/lib/teiserver/account/reports/tournament_report.ex deleted file mode 100644 index 0c45da552..000000000 --- a/lib/teiserver/account/reports/tournament_report.ex +++ /dev/null @@ -1,268 +0,0 @@ -defmodule Teiserver.Account.TournamentReport do - @moduledoc false - alias Teiserver.Account - alias Teiserver.Account.RatingLib - alias Teiserver.Game.MatchRatingLib - alias Teiserver.Helper.TimexHelper - import Teiserver.Helper.NumberHelper, only: [round: 2] - - @spec icon() :: String.t() - def icon, do: RatingLib.icon() - - @spec permissions() :: String.t() - def permissions, do: "Moderator" - - defp get_player_id("#" <> id_str), do: String.to_integer(id_str) - - defp get_player_id(name) do - Account.get_userid_from_name(name) - end - - @spec make_split_data(String.t()) :: %{non_neg_integer => {String.t(), non_neg_integer()}} - defp make_split_data(data) do - data - |> String.trim() - |> String.split("\n") - |> Enum.with_index() - |> Map.new(fn {row, team_idx} -> - id_map = - row - |> String.split(",") - |> Enum.map(&String.trim/1) - |> Enum.filter(fn - "" -> false - _name -> true - end) - |> Enum.map(fn name -> - {String.trim(name), get_player_id(name)} - end) - |> Enum.reject(fn {_name, n} -> n == nil end) - - {team_idx, id_map} - end) - end - - @spec run(Plug.Conn.t(), map()) :: {nil, map()} - def run(_conn, params) do - params = apply_defaults(params) - - # First we take our lines and break them into teams - split_data = make_split_data(params["names"] || "") - - name_to_id_map = - split_data - |> Map.values() - |> List.flatten() - |> Map.new() - - missing_names = - (params["names"] || "") - |> String.trim() - |> String.replace(",", "\n") - |> String.split("\n") - |> Enum.map(&String.trim/1) - |> Enum.reject(fn name -> - Map.has_key?(name_to_id_map, name) - end) - - if params["make_players"] == "true" do - id_list = name_to_id_map |> Map.values() |> Enum.reject(&(&1 == nil)) - - Account.list_users( - search: [id_in: id_list], - limit: Enum.count(id_list) - ) - |> Enum.each(fn user -> - new_roles = ["Tournament" | user.roles || []] |> Enum.uniq() - new_data = user.data |> Map.put("roles", new_roles) - - Account.update_user(user, %{"data" => new_data}) - Account.recache_user(user.id) - end) - - :timer.sleep(500) - end - - type_name = params["game_type"] - - {type_id, _type_name} = - case MatchRatingLib.rating_type_name_lookup()[type_name] do - nil -> - type_name = hd(MatchRatingLib.rating_type_list()) - {MatchRatingLib.rating_type_name_lookup()[type_name], type_name} - - v -> - {v, type_name} - end - - order_by = - case params["value_type"] do - "Leaderboard rating" -> "Leaderboard rating high to low" - "Game rating" -> "Rating value high to low" - "Skill value" -> "Skill high to low" - end - - ratings = - Account.list_ratings( - search: [ - rating_type_id: type_id, - user_id_in: Map.values(name_to_id_map), - season: MatchRatingLib.active_season() - ], - order_by: order_by, - preload: [:user], - limit: Enum.count(name_to_id_map) - ) - - found_ids = - ratings - |> Enum.map(fn r -> r.user_id end) - - no_ratings = - name_to_id_map - |> Enum.reject(fn {_name, id} -> id == nil or Enum.member?(found_ids, id) end) - |> Map.new() - |> Map.keys() - - teams_as_ids = - split_data - |> Map.values() - |> Enum.map_join("\n", fn team_data -> - team_data - |> Enum.map_join(", ", fn {_name, id} -> "##{id}" end) - end) - - rating_values = - ratings - |> Map.new(fn rating -> - value = - case params["value_type"] do - "Leaderboard rating" -> rating.leaderboard_rating - "Game rating" -> rating.rating_value - "Skill value" -> rating.skill - end - - {rating.user_id, value} - end) - - %{ - params: params, - name_to_id_map: name_to_id_map, - game_types: MatchRatingLib.rating_type_list(), - no_ratings: no_ratings, - ratings: ratings, - missing_names: missing_names, - teams_as_ids: teams_as_ids, - team_data: make_team_data(split_data, rating_values), - csv_data: make_csv_data(ratings, params["value_type"]) - } - end - - defp make_team_data(split_data, rating_values) do - split_data - |> Map.new(fn {team_id, members} -> - name_rating_pairs = - members - |> Enum.map(fn {name, userid} -> {name, rating_values[userid]} end) - |> Enum.reject(fn {_name, rating} -> rating == nil end) - - aggregate_data = - name_rating_pairs - |> Enum.map(fn {_name, rating} -> rating end) - |> aggregate_team_ratings() - - captain = - name_rating_pairs - |> Enum.sort_by(fn {_name, rating} -> rating end, &>=/2) - |> Enum.take(1) - - aggregate_data = - case captain do - [{name, rating}] -> - Map.merge(aggregate_data, %{ - captain_name: name, - captain_rating: rating |> round(2) - }) - - _other -> - aggregate_data - end - - {team_id, aggregate_data} - end) - end - - defp aggregate_team_ratings([]) do - %{ - mean: 0, - median: 0, - stdev: 0, - count: 0, - max: 0, - min: 0, - captain_name: "", - captain_rating: 0 - } - end - - defp aggregate_team_ratings(ratings) do - %{ - mean: Statistics.mean(ratings) |> round(2), - median: Statistics.median(ratings) |> round(2), - stdev: Statistics.stdev(ratings) |> round(2), - count: Enum.count(ratings), - max: Enum.max(ratings) |> round(2), - min: Enum.min(ratings) |> round(2), - captain_name: "", - captain_rating: 0 - } - end - - defp add_csv_headings(output, value_type) do - headings = [ - [ - "Position", - "UserId", - "Player", - "Registration date", - value_type - ] - ] - - headings ++ output - end - - defp make_csv_data(ratings, value_type) do - ratings - |> Enum.with_index() - |> Enum.map(fn {rating, index} -> - value = - case value_type do - "Leaderboard rating" -> rating.leaderboard_rating - "Game rating" -> rating.rating_value - "Skill value" -> rating.skill - end - - [ - index + 1, - rating.user.id, - rating.user.name, - TimexHelper.date_to_str(rating.user.inserted_at, format: :ymd), - value - ] - end) - |> add_csv_headings(value_type) - |> CSV.encode(separator: ?\t) - |> Enum.to_list() - end - - defp apply_defaults(params) do - Map.merge( - %{ - "game_type" => MatchRatingLib.rating_type_list() |> hd(), - "value_type" => "Leaderboard rating" - }, - Map.get(params, "report", %{}) - ) - end -end diff --git a/lib/teiserver/account/schemas/accolade.ex b/lib/teiserver/account/schemas/accolade.ex index 2b47a2c5e..ee96170c6 100644 --- a/lib/teiserver/account/schemas/accolade.ex +++ b/lib/teiserver/account/schemas/accolade.ex @@ -1,5 +1,8 @@ defmodule Teiserver.Account.Accolade do - @moduledoc false + @moduledoc """ + Accolade struct + """ + use TeiserverWeb, :schema typed_schema "teiserver_account_accolades" do diff --git a/lib/teiserver/application.ex b/lib/teiserver/application.ex index 4c41e3ec6..622fdf172 100644 --- a/lib/teiserver/application.ex +++ b/lib/teiserver/application.ex @@ -69,7 +69,6 @@ defmodule Teiserver.Application do # Global/singleton registries {Horde.Registry, [keys: :unique, members: :auto, name: Teiserver.ServerRegistry]}, {Horde.Registry, [keys: :unique, members: :auto, name: Teiserver.ThrottleRegistry]}, - {Horde.Registry, [keys: :unique, members: :auto, name: Teiserver.AccoladesRegistry]}, {Horde.Registry, [keys: :unique, members: :auto, name: Teiserver.ConsulRegistry]}, {Horde.Registry, [keys: :unique, members: :auto, name: Teiserver.BalancerRegistry]}, {Horde.Registry, [keys: :unique, members: :auto, name: Teiserver.LobbyRegistry]}, @@ -150,9 +149,6 @@ defmodule Teiserver.Application do {DynamicSupervisor, strategy: :one_for_one, name: Teiserver.Coordinator.BalancerDynamicSupervisor}, - # Accolades - {DynamicSupervisor, strategy: :one_for_one, name: Teiserver.Account.AccoladeSupervisor}, - # Achievements {Teiserver.Game.AchievementServer, name: Teiserver.Game.AchievementServer}, diff --git a/lib/teiserver/battle.ex b/lib/teiserver/battle.ex index cdd8eb89a..793b1fcc6 100644 --- a/lib/teiserver/battle.ex +++ b/lib/teiserver/battle.ex @@ -310,26 +310,6 @@ defmodule Teiserver.Battle do Match.changeset(match, %{}) end - # Not to be confused with protocol related adding, this - # tells the battle lobby to proceed as if the user was just accepted into - # the battle. It should never be called directly from a protocol - # related command, only via things like matchmaking our tourneys - # It is currently not actually used so might be ripe for removal - # @spec add_player_to_battle(T.userid(), T.lobby_id()) :: :ok | {:error, String.t()} - # def add_player_to_battle(userid, lobby_id) do - # case Teiserver.Client.get_client_by_id(userid) do - # nil -> - # {:error, "no client"} - # _ -> - # case lobby_exists?(lobby_id) do - # false -> - # {:error, "no battle"} - # true -> - # Teiserver.Lobby.accept_join_request(userid, lobby_id) - # end - # end - # end - @spec start_match(nil | T.lobby_id()) :: :ok def start_match(nil), do: :ok diff --git a/lib/teiserver/battle/tasks/battle_daily_cleanup_task.ex b/lib/teiserver/battle/tasks/battle_daily_cleanup_task.ex index 8a6e11880..fd00f2692 100644 --- a/lib/teiserver/battle/tasks/battle_daily_cleanup_task.ex +++ b/lib/teiserver/battle/tasks/battle_daily_cleanup_task.ex @@ -141,13 +141,6 @@ defmodule Teiserver.Battle.Tasks.CleanupTask do [ids] ) - # Update report groups - SQL.query!( - Repo, - "UPDATE moderation_report_groups SET match_id = NULL WHERE match_id = ANY($1)", - [ids] - ) - # Match specific things we want to delete SQL.query!( Repo, diff --git a/lib/teiserver/bridge/commands/post_command.ex b/lib/teiserver/bridge/commands/post_command.ex new file mode 100644 index 000000000..289cab031 --- /dev/null +++ b/lib/teiserver/bridge/commands/post_command.ex @@ -0,0 +1,124 @@ +defmodule Teiserver.Bridge.Commands.PostCommand do + @moduledoc """ + Calls the bot to post report or action in current channel + """ + alias Teiserver.Account + alias Teiserver.Bridge.DiscordBridgeBot + alias Teiserver.Communication + alias Teiserver.Config + alias Teiserver.Moderation + alias Teiserver.Moderation.ActionLib + require Logger + + @behaviour Teiserver.Bridge.BridgeCommandBehaviour + + @impl Teiserver.Bridge.BridgeCommandBehaviour + @spec name() :: String.t() + def name, do: "post" + + @impl Teiserver.Bridge.BridgeCommandBehaviour + @spec cmd_definition() :: map() + def cmd_definition do + %{ + name: "post", + description: "Post something into the current channel", + options: [ + %{ + # type = sub_command + type: 1, + name: "report", + description: "Post a report", + required: true, + options: [ + %{ + name: "id", + description: "ID of the report", + type: 4, + required: true + } + ] + }, + %{ + type: 1, + name: "action", + description: "Post an Action", + required: true, + options: [ + %{ + name: "id", + description: "ID of the action", + type: 4, + required: true + } + ] + }, + %{ + type: 1, + name: "profile", + description: "Post the link of a moderation profile", + required: true, + options: [ + %{ + name: "name", + description: "Name of the Account", + type: 3, + required: true + } + ] + } + ], + nsfw: false + } + end + + @impl Teiserver.Bridge.BridgeCommandBehaviour + @spec execute(interaction :: Nostrum.Struct.Interaction.t(), options_map :: map()) :: map() + def execute(interaction, _options_map) do + subcommand = Enum.at(interaction.data.options, 0) + args = Enum.at(subcommand.options, 0) + + content = + case subcommand.name do + "action" -> + case Moderation.get_action(args.value) do + nil -> + "No action with ID \"#{args.value}\" found" + + action -> + channel_id = + Config.get_site_config_cache("teiserver.Discord channel #moderation-actions") + + ActionLib.generate_discord_message_text(action) <> + "\n**Original:** https://discord.com/channels/#{Communication.get_guild_id()}/#{channel_id}/#{action.discord_message_id}" + end + + "report" -> + case Moderation.get_report(args.value, preload: [:reporter, :target]) do + nil -> + "No report with ID \"#{args.value}\" found" + + report -> + DiscordBridgeBot.get_report_message(report) + |> List.delete_at(-1) + |> List.insert_at( + -1, + "**Original:** https://discord.com/channels/#{Communication.get_guild_id()}/#{DiscordBridgeBot.get_channel_for_report_type(report.type)}/#{report.discord_message_id}" + ) + |> Enum.join("\n") + end + + "profile" -> + name = args.value + + case Account.get_user_by_name(name) do + nil -> + "User \"#{name}\" not found" + + user -> + "https://#{Application.get_env(:teiserver, TeiserverWeb.Endpoint)[:url][:host]}/moderation/report/user/#{user.id}" + end + end + + Communication.new_interaction_response(content) + end +end diff --git a/lib/teiserver/bridge/discord_bridge_bot.ex b/lib/teiserver/bridge/discord_bridge_bot.ex index 84d6a31c2..420735e53 100644 --- a/lib/teiserver/bridge/discord_bridge_bot.ex +++ b/lib/teiserver/bridge/discord_bridge_bot.ex @@ -93,7 +93,6 @@ defmodule Teiserver.Bridge.DiscordBridgeBot do # Stuff we might want to use def handle_event({:MESSAGE_CREATE, _message, _ws}) do - # Has an attachment :ignore end @@ -123,6 +122,10 @@ defmodule Teiserver.Bridge.DiscordBridgeBot do :ignore end + def handle_event({:CHANNEL_CREATE, _channel, _ws}) do + :ignore + end + def handle_event({:CHANNEL_UPDATE, _channel, _ws}) do :ignore end @@ -158,6 +161,7 @@ defmodule Teiserver.Bridge.DiscordBridgeBot do BridgeServer.cast_bridge(:READY) add_command(:textcb) add_command(:findreport) + add_command(:post) :ignore end @@ -205,6 +209,60 @@ defmodule Teiserver.Bridge.DiscordBridgeBot do ApplicationCommand.create_guild_command(Communication.get_guild_id(), command) end + # Teiserver.Bridge.DiscordBridgeBot.add_command(:post) + @spec add_command(atom) :: any + def add_command(:post) do + command = %{ + name: "post", + description: "Post something into the current channel", + options: [ + %{ + # type = sub_command + type: 1, + name: "report", + description: "Post a report", + options: [ + %{ + name: "id", + description: "ID of the report", + type: 4, + required: true + } + ] + }, + %{ + type: 1, + name: "action", + description: "Post an Action", + options: [ + %{ + name: "id", + description: "ID of the action", + type: 4, + required: true + } + ] + }, + %{ + type: 1, + name: "profile", + description: "Post the link of a moderation profile", + options: [ + %{ + name: "name", + description: "Name of the Account", + type: 3, + required: true + } + ] + } + ], + nsfw: false + } + + ApplicationCommand.create_guild_command(Communication.get_guild_id(), command) + end + # Teiserver.Bridge.DiscordBridgeBot.add_command(:textcb) @spec add_command(atom) :: any def add_command(:textcb) do @@ -300,29 +358,57 @@ defmodule Teiserver.Bridge.DiscordBridgeBot do end end + def get_report_message(report) do + host = Application.get_env(:teiserver, TeiserverWeb.Endpoint)[:url][:host] + url = "https://#{host}/moderation/report?target_id=#{report.target_id}" + + match_icon = + if is_nil(report.match_id) do + "" + else + ":crossed_swords:" + end + + [ + "# [Moderation report #{report.type}/#{report.sub_type}](#{url})#{match_icon}", + "**Target:** [#{report.target.name}](https://#{host}/moderation/report/user/#{report.target.id})", + "**Reporter:** [#{report.reporter.name}](https://#{host}/moderation/report/user/#{report.reporter.id})", + "**Reason:** #{format_link(report.extra_text)}" + ] ++ + cond do + not is_nil(report.result_id) -> + ["**Status:** Actioned :hammer:"] + + report.closed == true -> + ["**Status:** Closed :file_folder:"] + + true -> + ["**Status:** Open"] + end + end + + def get_channel_for_report_type(type) do + case type do + "actions" -> + Config.get_site_config_cache("teiserver.Discord channel #overwatch-reports") + + "chat" -> + Config.get_site_config_cache("teiserver.Discord channel #moderation-reports") + + _other -> + Logger.error("Unknown report type #{type}") + raise "Unknown report type #{type}" + end + end + # Teiserver.Moderation.get_report!(123) |> Teiserver.Bridge.DiscordBridgeBot.new_report() @spec new_report(Moderation.Report.t()) :: any def new_report(report) do - channel = - if report.type == "actions" do - Config.get_site_config_cache("teiserver.Discord channel #overwatch-reports") - else - Config.get_site_config_cache("teiserver.Discord channel #moderation-reports") - end + channel = get_channel_for_report_type(report.type) if channel do report = Moderation.get_report!(report.id, preload: [:reporter, :target]) - host = Application.get_env(:teiserver, TeiserverWeb.Endpoint)[:url][:host] - url = "https://#{host}/moderation/report?target_id=#{report.target_id}" - - match_icon = - if is_nil(report.match_id) do - "" - else - ":crossed_swords:" - end - outstanding_count = Moderation.list_outstanding_reports_against_user(report.target_id) |> Enum.count() @@ -339,14 +425,7 @@ defmodule Teiserver.Bridge.DiscordBridgeBot do "" end - msg = - [ - "# [Moderation report #{report.type}/#{report.sub_type}](#{url})#{match_icon}", - "**Target:** [#{report.target.name}](https://#{host}/moderation/report/user/#{report.target.id})", - "**Reporter:** [#{report.reporter.name}](https://#{host}/moderation/report/user/#{report.reporter.id})", - "**Reason:** #{format_link(report.extra_text)}", - "**Status:** Open" - ] + msg = get_report_message(report) reports = if is_nil(report.match_id) do diff --git a/lib/teiserver/coordinator/consul_commands.ex b/lib/teiserver/coordinator/consul_commands.ex index 98083b430..7f47d1fa0 100644 --- a/lib/teiserver/coordinator/consul_commands.ex +++ b/lib/teiserver/coordinator/consul_commands.ex @@ -9,7 +9,6 @@ defmodule Teiserver.Coordinator.ConsulCommands do alias Teiserver.CacheUser alias Teiserver.Chat.WordLib alias Teiserver.Client - alias Teiserver.Config alias Teiserver.Coordinator alias Teiserver.Coordinator.ConsulServer alias Teiserver.Coordinator.RikerssMemes @@ -86,11 +85,6 @@ defmodule Teiserver.Coordinator.ConsulCommands do "Host bosses are: #{boss_names}" end - tourney_mode = - if state.tournament_lobby do - "Tournament mode is enabled" - end - # Party info parties = Battle.list_lobby_players(state.lobby_id) @@ -131,7 +125,6 @@ defmodule Teiserver.Coordinator.ConsulCommands do "Team size and count are: #{state.host_teamsize} and #{state.host_teamcount}", "Balance algorithm is: #{state.balance_algorithm}", boss_string, - tourney_mode, "Maximum allowed number of players is #{max_player_count} (Host = #{state.host_teamsize * state.host_teamcount}, Coordinator = #{state.player_limit})", play_level_bounds, play_rank_bounds @@ -238,49 +231,6 @@ defmodule Teiserver.Coordinator.ConsulCommands do state end - def handle_command(%{command: "tournament", senderid: senderid, remaining: rem} = cmd, state) do - if Config.get_site_config_cache("teiserver.Allow tournament command") do - if Auth.has_any_role?(senderid, [ - "Moderator", - "Caster", - "Tournament player", - "TourneyPlayer" - ]) do - if rem |> String.trim() |> String.downcase() == "off" do - Battle.update_lobby_values(state.lobby_id, %{tournament: false}) - state = %{state | tournament_lobby: false} - ConsulServer.say_command(cmd, state) - else - Battle.update_lobby_values(state.lobby_id, %{tournament: true}) - # ChatLib.say(senderid, "!preset tourney", state.lobby_id) - send(self(), :recheck_membership) - state = %{state | tournament_lobby: true} - ConsulServer.say_command(cmd, state) - end - else - ChatLib.sayprivateex( - state.coordinator_id, - senderid, - "Only casters, tournament players and moderators can set tournament mode.", - state.lobby_id - ) - - state - end - else - Battle.update_lobby_values(state.lobby_id, %{tournament: false}) - - ChatLib.sayprivateex( - state.coordinator_id, - senderid, - "Tournament mode has been removed from this lobby.", - state.lobby_id - ) - - %{state | tournament_lobby: false} - end - end - def handle_command(%{command: "afks", senderid: senderid} = cmd, state) do min_diff_ms = 20_000 max_diff_s = 300 diff --git a/lib/teiserver/coordinator/consul_server.ex b/lib/teiserver/coordinator/consul_server.ex index 0247f0033..5b808c38b 100644 --- a/lib/teiserver/coordinator/consul_server.ex +++ b/lib/teiserver/coordinator/consul_server.ex @@ -28,7 +28,7 @@ defmodule Teiserver.Coordinator.ConsulServer do require Logger import Teiserver.Helper.NumberHelper, only: [int_parse: 1] - @always_allow ~w(status s y n follow joinq leaveq splitlobby afks roll password? tournament) + @always_allow ~w(status s y n follow joinq leaveq splitlobby afks roll password?) @boss_commands ~w(balancealgorithm gatekeeper welcome-message meme reset-approval rename minchevlevel maxchevlevel resetchevlevels resetratinglevels minratinglevel maxratinglevel setratinglevels) @host_commands ~w(specunready makeready settag speclock forceplay lobbyban lobbybanmult unban forcespec lock unlock makebalance set-config-teaser) @admin_commands ~w(shuffle) @@ -76,7 +76,7 @@ defmodule Teiserver.Coordinator.ConsulServer do def handle_call(:get_chobby_extra_data, _from, state) do keys = - ~w(lobby_policy_id tournament_lobby gatekeeper minimum_rating_to_play maximum_rating_to_play minimum_rank_to_play maximum_rank_to_play minimum_uncertainty_to_play maximum_uncertainty_to_play minimum_skill_to_play maximum_skill_to_play welcome_message player_limit)a + ~w(lobby_policy_id gatekeeper minimum_rating_to_play maximum_rating_to_play minimum_rank_to_play maximum_rank_to_play minimum_uncertainty_to_play maximum_uncertainty_to_play minimum_skill_to_play maximum_skill_to_play welcome_message player_limit)a result = state @@ -989,10 +989,6 @@ defmodule Teiserver.Coordinator.ConsulServer do client.shadowbanned -> {false, "Err"} - state.tournament_lobby == true and - not Auth.has_any_role?(userid, ["Caster", "TourneyPlayer", "Tournament player"]) -> - {false, "Tournament game"} - block_status == :blocking -> Telemetry.log_simple_lobby_event(userid, match_id, "join_refused.blocking") {false, "You are blocking too many players in this lobby"} @@ -1379,7 +1375,6 @@ defmodule Teiserver.Coordinator.ConsulServer do lobby_id: lobby_id, host_id: founder_id, lobby_policy_id: nil, - tournament_lobby: false, gatekeeper: "default", minimum_rating_to_play: 0, maximum_rating_to_play: 1000, diff --git a/lib/teiserver/game/servers/lobby_policy_bot_server.ex b/lib/teiserver/game/servers/lobby_policy_bot_server.ex index 7ee644a43..a18f00238 100644 --- a/lib/teiserver/game/servers/lobby_policy_bot_server.ex +++ b/lib/teiserver/game/servers/lobby_policy_bot_server.ex @@ -164,7 +164,6 @@ defmodule Teiserver.Game.LobbyPolicyBotServer do String.contains?(l.name, "ENGINE TEST") == false and l.passworded == false and l.locked == false and - l.tournament == false and l.in_progress == false and not String.contains?(l.name, "ENGINE TEST") end) diff --git a/lib/teiserver/helpers/string_helper.ex b/lib/teiserver/helpers/string_helper.ex index 1948b5045..d00652cdb 100644 --- a/lib/teiserver/helpers/string_helper.ex +++ b/lib/teiserver/helpers/string_helper.ex @@ -151,4 +151,14 @@ defmodule Teiserver.Helper.StringHelper do |> String.split("\n") |> Enum.map(&String.trim/1) end + + @doc """ + Gets the domain from an email address. For example, "user@example.com" + would return "example.com". + """ + @spec get_email_domain(String.t()) :: String.t() + def get_email_domain(email) do + [_first | rest] = String.split(email, "@") + Enum.join(rest, "") + end end diff --git a/lib/teiserver/libs/teiserver_configs.ex b/lib/teiserver/libs/teiserver_configs.ex index 5b5118782..7852585b4 100644 --- a/lib/teiserver/libs/teiserver_configs.ex +++ b/lib/teiserver/libs/teiserver_configs.ex @@ -75,15 +75,6 @@ defmodule Teiserver.TeiserverConfigs do default: true }) - add_site_config_type(%{ - key: "teiserver.Inform of new accolades", - section: "Accolades", - type: "boolean", - permissions: ["Server"], - description: "When set to true, players will be informed when they get a new accolade", - default: false - }) - add_site_config_type(%{ key: "teiserver.Accolade gift limit", section: "Accolades", @@ -338,16 +329,6 @@ defmodule Teiserver.TeiserverConfigs do default: 10 }) - add_site_config_type(%{ - key: "teiserver.Allow tournament command", - section: "Lobbies", - type: "boolean", - permissions: ["Admin"], - description: - "When set to true, the $tournament command will be able to be used. When disabled it can still be used but only to turn off tournament mode.", - default: false - }) - add_site_config_type(%{ key: "teiserver.Default player limit", section: "Lobbies", diff --git a/lib/teiserver/lobby.ex b/lib/teiserver/lobby.ex index 7dabb592b..8a79851df 100644 --- a/lib/teiserver/lobby.ex +++ b/lib/teiserver/lobby.ex @@ -108,7 +108,6 @@ defmodule Teiserver.Lobby do lobby_policy_id: nil, # Meta data - tournament: false, silence: false, in_progress: false, started_at: nil diff --git a/lib/teiserver/lobby/schemas/lobby_struct.ex b/lib/teiserver/lobby/schemas/lobby_struct.ex index 5e3cea327..b4c6d36ba 100644 --- a/lib/teiserver/lobby/schemas/lobby_struct.ex +++ b/lib/teiserver/lobby/schemas/lobby_struct.ex @@ -49,7 +49,6 @@ defmodule Teiserver.Lobby.LobbyStruct do # External references lobby_policy_id: nil, queue_id: nil, - tournament_id: nil, # Consul server stuff gatekeeper: "default", diff --git a/lib/teiserver/lobby/servers/lobby_index_throttle.ex b/lib/teiserver/lobby/servers/lobby_index_throttle.ex index 00078b1ac..b63f82369 100644 --- a/lib/teiserver/lobby/servers/lobby_index_throttle.ex +++ b/lib/teiserver/lobby/servers/lobby_index_throttle.ex @@ -17,9 +17,6 @@ defmodule Teiserver.Battle.LobbyIndexThrottle do :public -> state.public_lobby_list - :tournament -> - state.tournament_lobby_list - _unknown -> Logger.error("No get_cache handler for #{cache}") [] @@ -48,7 +45,7 @@ defmodule Teiserver.Battle.LobbyIndexThrottle do lobby = Map.take( lobby, - ~w(id name map_name passworded locked public tournament in_progress member_count player_count)a + ~w(id name map_name passworded locked public in_progress member_count player_count)a ) Map.merge(lobby, %{ @@ -63,20 +60,12 @@ defmodule Teiserver.Battle.LobbyIndexThrottle do complete_list |> Enum.reject(fn lobby -> lobby.passworded or - lobby.locked or - lobby.tournament - end) - - tournament_list = - complete_list - |> Enum.filter(fn lobby -> - lobby.tournament + lobby.locked end) %{ complete_lobby_list: complete_list, - public_lobby_list: public_list, - tournament_lobby_list: tournament_list + public_lobby_list: public_list } end @@ -110,7 +99,6 @@ defmodule Teiserver.Battle.LobbyIndexThrottle do %{ complete_lobby_list: [], public_lobby_list: [], - tournament_lobby_list: [], last_update: System.system_time(:second) }} end diff --git a/lib/teiserver/moderation.ex b/lib/teiserver/moderation.ex index e1a2a3733..4baf73cc2 100644 --- a/lib/teiserver/moderation.ex +++ b/lib/teiserver/moderation.ex @@ -18,7 +18,6 @@ defmodule Teiserver.Moderation do alias Teiserver.Moderation.ProposalVoteLib alias Teiserver.Moderation.RefreshUserRestrictionsTask alias Teiserver.Moderation.Report - alias Teiserver.Moderation.ReportGroupLib alias Teiserver.Moderation.ReportLib alias Teiserver.Moderation.Response alias Teiserver.Moderation.ResponseLib @@ -254,66 +253,6 @@ defmodule Teiserver.Moderation do Report.changeset(report, %{}) end - def create_report_group_and_report(report_params) do - report_group = get_or_make_report_group(report_params.target_id, report_params.match_id) - - report_params = - Map.merge(report_params, %{ - report_group_id: report_group.id - }) - - case create_report(report_params) do - {:ok, report} -> - {:ok, report_group} = - update_report_group(report_group, %{ - report_count: report_group.report_count + 1 - }) - - {:ok, report_group, report} - - result -> - result - end - end - - @spec list_report_groups() :: [ComplexServerEventType.t()] - defdelegate list_report_groups(), to: ReportGroupLib - - @spec list_report_groups(list) :: [ComplexServerEventType.t()] - defdelegate list_report_groups(args), to: ReportGroupLib - - @spec count_report_groups(list) :: integer() - defdelegate count_report_groups(args), to: ReportGroupLib - - @spec get_report_group!(non_neg_integer) :: ComplexServerEventType.t() - defdelegate get_report_group!(id), to: ReportGroupLib - - @spec get_report_group!(non_neg_integer, list) :: ComplexServerEventType.t() - defdelegate get_report_group!(id, args), to: ReportGroupLib - - @spec create_report_group() :: {:ok, ComplexServerEventType.t()} | {:error, Ecto.Changeset} - defdelegate create_report_group(), to: ReportGroupLib - - @spec create_report_group(map) :: {:ok, ComplexServerEventType.t()} | {:error, Ecto.Changeset} - defdelegate create_report_group(attrs), to: ReportGroupLib - - @spec update_report_group(ComplexServerEventType.t(), map) :: - {:ok, ComplexServerEventType.t()} | {:error, Ecto.Changeset} - defdelegate update_report_group(report_group, attrs), to: ReportGroupLib - - @spec delete_report_group(ComplexServerEventType) :: - {:ok, ComplexServerEventType} | {:error, Ecto.Changeset} - defdelegate delete_report_group(report_group), to: ReportGroupLib - - @spec change_report_group(ComplexServerEventType) :: Ecto.Changeset - defdelegate change_report_group(report_group), to: ReportGroupLib - - @spec change_report_group(ComplexServerEventType, map) :: Ecto.Changeset - defdelegate change_report_group(report_group, attrs), to: ReportGroupLib - - @spec get_or_make_report_group(T.userid(), T.match_id() | nil) :: ReportGroup.t() - defdelegate get_or_make_report_group(target_id, match_id), to: ReportGroupLib - @spec response_query(List.t()) :: Ecto.Query.t() def response_query(args) do response_query(nil, args) @@ -591,8 +530,8 @@ defmodule Teiserver.Moderation do nil """ - @spec get_action(Integer.t() | List.t()) :: Action.t() - @spec get_action(Integer.t(), List.t()) :: Action.t() + @spec get_action(Integer.t() | List.t()) :: Action.t() | nil + @spec get_action(Integer.t(), List.t()) :: Action.t() | nil def get_action(id) when not is_list(id) do action_query(id, []) |> Repo.one() diff --git a/lib/teiserver/moderation/libs/action_lib.ex b/lib/teiserver/moderation/libs/action_lib.ex index 28c2424b1..4fbf267e2 100644 --- a/lib/teiserver/moderation/libs/action_lib.ex +++ b/lib/teiserver/moderation/libs/action_lib.ex @@ -179,12 +179,6 @@ defmodule Teiserver.Moderation.ActionLib do def preload(query, preloads) do query = if :target in preloads, do: _preload_target(query), else: query - query = if :report_groups in preloads, do: _preload_report_groups(query), else: query - - query = - if :report_group_reports_and_reporters in preloads, - do: _preload_report_group_reports_and_reporters(query), - else: query query end @@ -196,22 +190,6 @@ defmodule Teiserver.Moderation.ActionLib do preload: [target: targets] end - @spec _preload_report_groups(Ecto.Query.t()) :: Ecto.Query.t() - def _preload_report_groups(query) do - from actions in query, - left_join: report_groups in assoc(actions, :report_groups), - preload: [report_groups: report_groups] - end - - @spec _preload_report_group_reports_and_reporters(Ecto.Query.t()) :: Ecto.Query.t() - def _preload_report_group_reports_and_reporters(query) do - from actions in query, - left_join: report_groups in assoc(actions, :report_group), - left_join: reports in assoc(report_groups, :reports), - left_join: reporters in assoc(reports, :reporter), - preload: [report_group: {report_groups, reports: {reports, reporter: reporters}}] - end - def generate_discord_message_text(nil), do: nil def generate_discord_message_text(action) do diff --git a/lib/teiserver/moderation/libs/report_group_lib.ex b/lib/teiserver/moderation/libs/report_group_lib.ex deleted file mode 100644 index 1bc93b5ce..000000000 --- a/lib/teiserver/moderation/libs/report_group_lib.ex +++ /dev/null @@ -1,190 +0,0 @@ -defmodule Teiserver.Moderation.ReportGroupLib do - @moduledoc false - - alias Teiserver.Moderation - alias Teiserver.Moderation.ReportGroup - alias Teiserver.Moderation.ReportGroupQueries - use TeiserverWeb, :library_newform - - @spec colour :: atom - def colour, do: :warning - - @spec icon() :: String.t() - def icon, do: "fa-house-flag" - - @spec make_favourite(ReportGroup.t()) :: map() - def make_favourite(report_group) do - %{ - type_colour: Moderation.colour(), - type_icon: Moderation.icon(), - item_id: report_group.id, - item_type: "moderation_report_group", - item_colour: colour(), - item_icon: icon(), - item_label: "#{report_group.target.name}", - url: "/moderation/overwatch/report_group/#{report_group.id}" - } - end - - @doc """ - Returns the list of report_groups. - - ## Examples - - iex> list_report_groups() - [%ReportGroup{}, ...] - - """ - @spec list_report_groups(list) :: list - def list_report_groups(args \\ []) do - args - |> ReportGroupQueries.query_report_groups() - |> Repo.all() - end - - @spec count_report_groups(list) :: integer() - def count_report_groups(args \\ []) do - args - |> ReportGroupQueries.query_report_groups() - |> Repo.aggregate(:count, :id) - end - - @doc """ - Gets a single report_group. - - Raises `Ecto.NoResultsError` if the ReportGroup does not exist. - - ## Examples - - iex> get_report_group!(123) - %ReportGroup{} - - iex> get_report_group!(456) - ** (Ecto.NoResultsError) - - """ - def get_report_group!(id), do: Repo.get!(ReportGroup, id) - - def get_report_group!(id, args) do - args = args ++ [id: id] - - args - |> ReportGroupQueries.query_report_groups() - |> Repo.one!() - end - - @spec get_report_group(non_neg_integer(), T.match_id()) :: ReportGroup.t() | nil - def get_report_group(id, args) when is_list(args) do - args = args ++ [id: id] - - args - |> ReportGroupQueries.query_report_groups() - |> Repo.one() - end - - def get_report_group(target_id, match_id) do - ReportGroupQueries.query_report_groups( - where: [ - target_id: target_id, - match_id: match_id - ] - ) - |> Repo.one() - end - - @doc """ - Creates a report_group. - - ## Examples - - iex> create_report_group(%{field: value}) - {:ok, %ReportGroup{}} - - iex> create_report_group(%{field: bad_value}) - {:error, %Ecto.Changeset{}} - - """ - def create_report_group(attrs \\ %{}) do - %ReportGroup{} - |> ReportGroup.changeset(attrs) - |> Repo.insert() - end - - @doc """ - Updates a report_group. - - ## Examples - iex> update_report_group(report_group, %{field: new_value}) - {:ok, %ReportGroup{}} - - iex> update_report_group(report_group, %{field: bad_value}) - {:error, %Ecto.Changeset{}} - - """ - def update_report_group(%ReportGroup{} = report_group, attrs) do - report_group - |> ReportGroup.changeset(attrs) - |> Repo.update() - end - - @doc """ - Deletes a report_group. - - ## Examples - - iex> delete_report_group(report_group) - {:ok, %ReportGroup{}} - - iex> delete_report_group(report_group) - {:error, %Ecto.Changeset{}} - - """ - def delete_report_group(%ReportGroup{} = report_group) do - Repo.delete(report_group) - end - - @doc """ - Returns an `%Ecto.Changeset{}` for tracking report_group changes. - - ## Examples - - iex> change_report_group(report_group) - %Ecto.Changeset{data: %ReportGroup{}} - - """ - def change_report_group(%ReportGroup{} = report_group, attrs \\ %{}) do - ReportGroup.changeset(report_group, attrs) - end - - @spec get_or_make_report_group(T.userid(), T.match_id()) :: ReportGroup.t() - def get_or_make_report_group(target_id, match_id) when is_integer(match_id) do - case get_report_group(target_id, match_id) do - nil -> - {:ok, rg} = create_report_group(%{target_id: target_id, match_id: match_id}) - rg - - r -> - r - end - end - - def get_or_make_report_group(target_id, nil) do - report_group = - get_report_group(nil, - where: [ - target_id: target_id, - match_id: false, - closed: false - ] - ) - - case report_group do - nil -> - {:ok, rg} = create_report_group(%{target_id: target_id}) - rg - - _existing -> - report_group - end - end -end diff --git a/lib/teiserver/moderation/queries/report_group_queries.ex b/lib/teiserver/moderation/queries/report_group_queries.ex deleted file mode 100644 index 8344c7ddb..000000000 --- a/lib/teiserver/moderation/queries/report_group_queries.ex +++ /dev/null @@ -1,150 +0,0 @@ -defmodule Teiserver.Moderation.ReportGroupQueries do - @moduledoc false - - alias Teiserver.Moderation.ReportGroup - use TeiserverWeb, :queries - - # Queries - @spec query_report_groups(list) :: Ecto.Query.t() - def query_report_groups(args) do - query = from(report_groups in ReportGroup) - - query - |> do_where(id: args[:id]) - |> do_where(args[:where]) - |> do_preload(args[:preload]) - |> do_order_by(args[:order_by]) - |> query_select(args[:select]) - |> limit_query(args[:limit]) - |> offset_query(args[:offset]) - end - - @spec do_where(Ecto.Query.t(), list | map | nil) :: Ecto.Query.t() - defp do_where(query, nil), do: query - - defp do_where(query, params) do - params - |> Enum.reduce(query, fn {key, value}, query_acc -> - _where(query_acc, key, value) - end) - end - - @spec _where(Ecto.Query.t(), atom(), any()) :: Ecto.Query.t() - defp _where(query, _key, ""), do: query - defp _where(query, _key, nil), do: query - - defp _where(query, :id, id) do - from report_groups in query, - where: report_groups.id == ^id - end - - defp _where(query, :id_in, id_list) do - from report_groups in query, - where: report_groups.id in ^id_list - end - - defp _where(query, :target_id, target_id) do - from report_groups in query, - where: report_groups.target_id == ^target_id - end - - defp _where(query, :match_id, false) do - from report_groups in query, - where: is_nil(report_groups.match_id) - end - - defp _where(query, :match_id, match_id) do - from report_groups in query, - where: report_groups.match_id == ^match_id - end - - defp _where(query, :closed, closed) do - from report_groups in query, - where: report_groups.closed == ^closed - end - - defp _where(query, :actioned, true) do - from report_groups in query, - where: report_groups.action_count > 0 - end - - defp _where(query, :actioned, false) do - from report_groups in query, - where: report_groups.action_count == 0 - end - - defp _where(query, :actioned, _value), do: query - - defp _where(query, :inserted_after, datetime) do - from report_groups in query, - where: report_groups.inserted_at >= ^datetime - end - - defp _where(query, :has_reports_of_kind, kind) do - from report_groups in query, - join: reports in assoc(report_groups, :reports), - where: fragment("? ~* ?", reports.type, ^kind) - end - - @spec do_order_by(Ecto.Query.t(), list | nil) :: Ecto.Query.t() - defp do_order_by(query, nil), do: query - - defp do_order_by(query, params) when is_list(params) do - params - |> Enum.reduce(query, fn key, query_acc -> - _order_by(query_acc, key) - end) - end - - defp do_order_by(query, params), do: do_order_by(query, [params]) - - defp _order_by(query, nil), do: query - - defp _order_by(query, "Newest first") do - from report_groups in query, - order_by: [desc: report_groups.updated_at] - end - - defp _order_by(query, "Oldest first") do - from report_groups in query, - order_by: [asc: report_groups.updated_at] - end - - @spec do_preload(Ecto.Query.t(), list() | nil) :: Ecto.Query.t() - defp do_preload(query, nil), do: query - - defp do_preload(query, preloads) do - preloads - |> Enum.reduce(query, fn key, query_acc -> - _preload(query_acc, key) - end) - end - - defp _preload(query, :target) do - from report_groups in query, - join: targets in assoc(report_groups, :target), - preload: [target: targets] - end - - defp _preload(query, :actions) do - from report_groups in query, - left_join: actions in assoc(report_groups, :actions), - preload: [actions: actions] - end - - defp _preload(query, :reports) do - from report_groups in query, - left_join: reports in assoc(report_groups, :reports), - preload: [reports: reports] - - # TODO add reporters here - end - - # defp _preload(query, :users) do - # from report_groups in query, - # join: tos in assoc(report_groups, :to), - # preload: [to: tos], - # join: froms in assoc(report_groups, :from), - # preload: [from: froms] - # end -end diff --git a/lib/teiserver/moderation/schemas/action.ex b/lib/teiserver/moderation/schemas/action.ex index 08ed0d590..c179451dc 100644 --- a/lib/teiserver/moderation/schemas/action.ex +++ b/lib/teiserver/moderation/schemas/action.ex @@ -19,9 +19,6 @@ defmodule Teiserver.Moderation.Action do field :discord_message_id, :integer - # No longer set nowadays - belongs_to :report_group, Teiserver.Moderation.ReportGroup - timestamps() end @@ -35,7 +32,7 @@ defmodule Teiserver.Moderation.Action do struct |> cast( params, - ~w(target_id report_group_id reason restrictions score_modifier expires notes hidden discord_message_id appeal_status)a + ~w(target_id reason restrictions score_modifier expires notes hidden discord_message_id appeal_status)a ) |> validate_required(~w(target_id reason restrictions expires score_modifier)a) |> adjust_restrictions() diff --git a/lib/teiserver/moderation/schemas/report.ex b/lib/teiserver/moderation/schemas/report.ex index fdd7370f7..ce6a6b18a 100644 --- a/lib/teiserver/moderation/schemas/report.ex +++ b/lib/teiserver/moderation/schemas/report.ex @@ -15,7 +15,6 @@ defmodule Teiserver.Moderation.Report do belongs_to :match, Teiserver.Battle.Match field :relationship, :string belongs_to :result, Teiserver.Moderation.Action - belongs_to :report_group, Teiserver.Moderation.ReportGroup has_many :responses, Teiserver.Moderation.Response @@ -31,7 +30,7 @@ defmodule Teiserver.Moderation.Report do struct |> cast( params, - ~w(reporter_id target_id type sub_type extra_text match_id discord_message_id relationship result_id closed report_group_id)a + ~w(reporter_id target_id type sub_type extra_text match_id discord_message_id relationship result_id closed)a ) |> validate_required(~w(reporter_id target_id type sub_type closed)a) end diff --git a/lib/teiserver/moderation/schemas/report_group.ex b/lib/teiserver/moderation/schemas/report_group.ex deleted file mode 100644 index 5e9e48e8a..000000000 --- a/lib/teiserver/moderation/schemas/report_group.ex +++ /dev/null @@ -1,39 +0,0 @@ -defmodule Teiserver.Moderation.ReportGroup do - @moduledoc false - use TeiserverWeb, :schema - - typed_schema "moderation_report_groups" do - belongs_to :target, Teiserver.Account.User - belongs_to :match, Teiserver.Battle.Match - - field :closed, :boolean, default: false - field :report_count, :integer, default: 0 - field :vote_count, :integer, default: 0 - field :action_count, :integer, default: 0 - - has_many :actions, Teiserver.Moderation.Action - has_many :reports, Teiserver.Moderation.Report - has_many :report_group_votes, Teiserver.Moderation.ReportGroupVote - has_many :report_group_messages, Teiserver.Moderation.ReportGroupMessage - - timestamps() - end - - @spec changeset(map(), map()) :: Ecto.Changeset.t() - def changeset(struct, params \\ %{}) do - struct - |> cast( - params, - ~w(target_id match_id report_count vote_count action_count closed)a - ) - |> validate_required(~w(target_id)a) - end - - @spec authorize(atom(), Plug.Conn.t(), map()) :: bool() - def authorize(:index, conn, _params), do: allow?(conn, "Overwatch") - def authorize(:search, conn, _params), do: allow?(conn, "Overwatch") - def authorize(:show, conn, _params), do: allow?(conn, "Overwatch") - def authorize(:user, conn, _params), do: allow?(conn, "Overwatch") - def authorize(:respond, conn, _params), do: allow?(conn, "Overwatch") - def authorize(_action, conn, _params), do: allow?(conn, "Moderator") -end diff --git a/lib/teiserver/moderation/schemas/report_group_message.ex b/lib/teiserver/moderation/schemas/report_group_message.ex deleted file mode 100644 index be0c03647..000000000 --- a/lib/teiserver/moderation/schemas/report_group_message.ex +++ /dev/null @@ -1,35 +0,0 @@ -defmodule Teiserver.Moderation.ReportGroupMessage do - @moduledoc false - use TeiserverWeb, :schema - - typed_schema "moderation_report_group_messages" do - belongs_to :report_group, Teiserver.Moderation.ReportGroup - belongs_to :user, Teiserver.Account.User - - field :content, :string - - timestamps() - end - - @spec changeset(map(), map()) :: Ecto.Changeset.t() - def changeset(struct, params \\ %{}) do - params = - params - |> trim_strings(~w(content)a) - - struct - |> cast( - params, - ~w(report_group_id user_id content)a - ) - |> validate_required(~w(report_group_id user_id content)a) - end - - @spec authorize(atom(), Plug.Conn.t(), map()) :: bool() - def authorize(:index, conn, _params), do: allow?(conn, "Overwatch") - def authorize(:search, conn, _params), do: allow?(conn, "Overwatch") - def authorize(:show, conn, _params), do: allow?(conn, "Overwatch") - def authorize(:user, conn, _params), do: allow?(conn, "Overwatch") - def authorize(:respond, conn, _params), do: allow?(conn, "Overwatch") - def authorize(_action, conn, _params), do: allow?(conn, "Moderator") -end diff --git a/lib/teiserver/moderation/schemas/report_group_vote.ex b/lib/teiserver/moderation/schemas/report_group_vote.ex deleted file mode 100644 index c16d8c540..000000000 --- a/lib/teiserver/moderation/schemas/report_group_vote.ex +++ /dev/null @@ -1,36 +0,0 @@ -defmodule Teiserver.Moderation.ReportGroupVote do - @moduledoc false - use TeiserverWeb, :schema - - typed_schema "moderation_report_group_votes" do - belongs_to :report_group, Teiserver.Moderation.ReportGroup - belongs_to :user, Teiserver.Account.User - - field :action, :string - field :accuracy, :string - - timestamps() - end - - @spec changeset(map(), map()) :: Ecto.Changeset.t() - def changeset(struct, params \\ %{}) do - params = - params - |> trim_strings(~w(action accuracy)a) - - struct - |> cast( - params, - ~w(report_group_id user_id action accuracy)a - ) - |> validate_required(~w(report_group_id user_id action accuracy)a) - end - - @spec authorize(atom(), Plug.Conn.t(), map()) :: bool() - def authorize(:index, conn, _params), do: allow?(conn, "Overwatch") - def authorize(:search, conn, _params), do: allow?(conn, "Overwatch") - def authorize(:show, conn, _params), do: allow?(conn, "Overwatch") - def authorize(:user, conn, _params), do: allow?(conn, "Overwatch") - def authorize(:respond, conn, _params), do: allow?(conn, "Overwatch") - def authorize(_action, conn, _params), do: allow?(conn, "Moderator") -end diff --git a/lib/teiserver/servers/telemetry_server.ex b/lib/teiserver/servers/telemetry_server.ex index bb3014ca5..00fb9e9ec 100644 --- a/lib/teiserver/servers/telemetry_server.ex +++ b/lib/teiserver/servers/telemetry_server.ex @@ -235,7 +235,6 @@ defmodule Teiserver.Telemetry.TelemetryServer do process_counts = %{ system_servers: Horde.Registry.count(Teiserver.ServerRegistry), throttle_servers: Horde.Registry.count(Teiserver.ThrottleRegistry), - accolade_servers: Horde.Registry.count(Teiserver.AccoladesRegistry), consul_servers: Horde.Registry.count(Teiserver.ConsulRegistry), balancer_servers: Horde.Registry.count(Teiserver.BalancerRegistry), lobby_servers: Horde.Registry.count(Teiserver.LobbyRegistry), diff --git a/lib/teiserver/startup.ex b/lib/teiserver/startup.ex index 9fef1fa51..1a830b6ba 100644 --- a/lib/teiserver/startup.ex +++ b/lib/teiserver/startup.ex @@ -2,7 +2,6 @@ defmodule Teiserver.Startup do @moduledoc false alias Phoenix.PubSub - alias Teiserver.Account.AccoladeLib alias Teiserver.Battle alias Teiserver.Bridge.CommandLib alias Teiserver.Communication @@ -41,13 +40,6 @@ defmodule Teiserver.Startup do end) end - if Application.get_env(:teiserver, Teiserver)[:enable_accolade_mode] do - spawn(fn -> - :timer.sleep(200) - AccoladeLib.start_accolade_server() - end) - end - Teiserver.cache_put(:application_metadata_cache, "teiserver_partial_startup_completed", true) Teiserver.cache_put(:application_metadata_cache, "teiserver_full_startup_completed", true) diff --git a/lib/teiserver_web/controllers/moderation/action_controller.ex b/lib/teiserver_web/controllers/moderation/action_controller.ex index 548e10cfb..9e3c71b11 100644 --- a/lib/teiserver_web/controllers/moderation/action_controller.ex +++ b/lib/teiserver_web/controllers/moderation/action_controller.ex @@ -412,12 +412,6 @@ defmodule TeiserverWeb.Moderation.ActionController do def delete(conn, %{"id" => id}) do action = Moderation.get_action!(id) - # Update any reports which were assigned to this - # action.report_groups - # |> Enum.each(fn report -> - # Moderation.update_report(report, %{result_id: nil}) - # end) - Moderation.delete_action(action) if action.discord_message_id do diff --git a/lib/teiserver_web/controllers/report/report_controller.ex b/lib/teiserver_web/controllers/report/report_controller.ex index f9009aff0..a64113fcd 100644 --- a/lib/teiserver_web/controllers/report/report_controller.ex +++ b/lib/teiserver_web/controllers/report/report_controller.ex @@ -77,9 +77,6 @@ defmodule TeiserverWeb.Report.ReportController do "open_skill" -> Teiserver.Account.OpenSkillReport - "tournament" -> - Teiserver.Account.TournamentReport - "microblog" -> Teiserver.Communication.MicroblogReport diff --git a/lib/teiserver_web/live/admin_dashboard/index.ex b/lib/teiserver_web/live/admin_dashboard/index.ex index a7b42f3bd..f389f288f 100644 --- a/lib/teiserver_web/live/admin_dashboard/index.ex +++ b/lib/teiserver_web/live/admin_dashboard/index.ex @@ -2,7 +2,6 @@ defmodule TeiserverWeb.AdminDashLive.Index do alias Phoenix.LiveView.Socket alias Phoenix.PubSub alias Teiserver - alias Teiserver.Account.AccoladeLib alias Teiserver.Admin.AdminLib alias Teiserver.Battle alias Teiserver.Battle.MatchMonitorServer @@ -162,7 +161,6 @@ defmodule TeiserverWeb.AdminDashLive.Index do server_pids = [ {"Lobby ID server", lobby_id_server_pid}, {"Coordinator", Coordinator.get_coordinator_pid()}, - {"Accolades", AccoladeLib.get_accolade_bot_pid()}, {"Match Monitor", MatchMonitorServer.get_match_monitor_pid()}, {"Automod", AutomodServer.get_automod_pid()}, {"Discord Bridge Bot", BridgeServer.get_bridge_pid()} diff --git a/lib/teiserver_web/live/battles/battle_components.ex b/lib/teiserver_web/live/battles/battle_components.ex index ec4bfe243..74f80621e 100644 --- a/lib/teiserver_web/live/battles/battle_components.ex +++ b/lib/teiserver_web/live/battles/battle_components.ex @@ -48,15 +48,6 @@ defmodule TeiserverWeb.Battle.BattleComponents do > Leaderboard - - <.sub_menu_button - bsname={@view_colour} - icon="fa-trophy" - active={@active == "tournaments"} - url={~p"/tournament/lobbies"} - > - Tournaments - """ end diff --git a/lib/teiserver_web/live/battles/lobbies/chat.ex b/lib/teiserver_web/live/battles/lobbies/chat.ex index 88e1556ca..1d83b7c88 100644 --- a/lib/teiserver_web/live/battles/lobbies/chat.ex +++ b/lib/teiserver_web/live/battles/lobbies/chat.ex @@ -1,7 +1,6 @@ defmodule TeiserverWeb.Battle.LobbyLive.Chat do alias Phoenix.PubSub alias Teiserver.Account - alias Teiserver.Account.Auth alias Teiserver.Battle alias Teiserver.CacheUser alias Teiserver.Chat @@ -53,15 +52,6 @@ defmodule TeiserverWeb.Battle.LobbyLive.Chat do lobby == nil -> index_redirect(socket) - lobby.tournament and - not Auth.has_any_role?(current_user.id, [ - "Moderator", - "Caster", - "TourneyPlayer", - "Tournament player" - ]) -> - index_redirect(socket) - (lobby.locked or lobby.passworded) and not allow?(socket, "Moderator") -> index_redirect(socket) diff --git a/lib/teiserver_web/live/battles/lobbies/index.ex b/lib/teiserver_web/live/battles/lobbies/index.ex index 6513467bf..56061d395 100644 --- a/lib/teiserver_web/live/battles/lobbies/index.ex +++ b/lib/teiserver_web/live/battles/lobbies/index.ex @@ -168,15 +168,11 @@ defmodule TeiserverWeb.Battle.LobbyLive.Index do defp filter_lobbies(lobbies, %{assigns: %{moderator: moderator}} = _socket) do if moderator do lobbies - |> Enum.reject(fn lobby -> - lobby.tournament - end) else lobbies |> Enum.reject(fn lobby -> lobby.locked or - lobby.passworded or - lobby.tournament + lobby.passworded end) end end diff --git a/lib/teiserver_web/live/battles/lobbies/show.ex b/lib/teiserver_web/live/battles/lobbies/show.ex index ca608663b..84dd5db30 100644 --- a/lib/teiserver_web/live/battles/lobbies/show.ex +++ b/lib/teiserver_web/live/battles/lobbies/show.ex @@ -89,9 +89,6 @@ defmodule TeiserverWeb.Battle.LobbyLive.Show do lobby == nil -> index_redirect(socket) - lobby.tournament -> - index_redirect(socket) - (lobby.locked or lobby.passworded) and not allow?(socket, "Moderator") -> index_redirect(socket) diff --git a/lib/teiserver_web/live/general/home/index.html.heex b/lib/teiserver_web/live/general/home/index.html.heex index f618edb97..0d3fa6211 100644 --- a/lib/teiserver_web/live/general/home/index.html.heex +++ b/lib/teiserver_web/live/general/home/index.html.heex @@ -19,15 +19,6 @@ Appeals - <.menu_card - :if={allow?(@current_user, "Overwatch")} - icon={Teiserver.Moderation.overwatch_icon()} - icon_class="fa-solid" - url={~p"/moderation/overwatch"} - > - Overwatch - - <.menu_card :if={allow?(@current_user, "Contributor")} icon={StylingHelper.icon(:summary)} diff --git a/lib/teiserver_web/live/moderation/moderation_components.ex b/lib/teiserver_web/live/moderation/moderation_components.ex index d74af5687..1dd31e862 100644 --- a/lib/teiserver_web/live/moderation/moderation_components.ex +++ b/lib/teiserver_web/live/moderation/moderation_components.ex @@ -14,15 +14,6 @@ defmodule TeiserverWeb.Moderation.ModerationComponents do def sub_menu(assigns) do ~H""" diff --git a/lib/teiserver_web/templates/moderation/general/index.html.heex b/lib/teiserver_web/templates/moderation/general/index.html.heex index 5047e3c89..55d5bc864 100644 --- a/lib/teiserver_web/templates/moderation/general/index.html.heex +++ b/lib/teiserver_web/templates/moderation/general/index.html.heex @@ -1,8 +1,4 @@
- <.menu_card icon={Teiserver.Moderation.overwatch_icon()} url={~p"/moderation/overwatch"}> - Overwatch - - {central_component("menu_card", icon: Teiserver.Moderation.ReportLib.icon(), name: "reports", diff --git a/lib/teiserver_web/templates/report/general/index.html.heex b/lib/teiserver_web/templates/report/general/index.html.heex index 84199f38f..2b487e7f8 100644 --- a/lib/teiserver_web/templates/report/general/index.html.heex +++ b/lib/teiserver_web/templates/report/general/index.html.heex @@ -199,16 +199,6 @@ )} <% end %> - <%= if allow?(@current_user, Teiserver.Account.TournamentReport.permissions()) do %> - {central_component("menu_card", - size: :small, - icon: "fa-solid fa-ranking-star", - name: "tournament", - text: "Tournament", - link: Routes.ts_reports_report_path(@conn, :show, "tournament") - )} - <% end %> - <%= if allow?(@current_user, Teiserver.Account.LeaderboardReport.permissions()) do %> {central_component("menu_card", size: :small, diff --git a/lib/teiserver_web/templates/report/report/section_menu.html.heex b/lib/teiserver_web/templates/report/report/section_menu.html.heex index 5b27bc6d6..5f2974937 100644 --- a/lib/teiserver_web/templates/report/report/section_menu.html.heex +++ b/lib/teiserver_web/templates/report/report/section_menu.html.heex @@ -110,17 +110,6 @@ )} <% end %> -<%= if allow?(@current_user, Teiserver.Account.TournamentReport.permissions()) do %> - {central_component("section_menu_button", - name: "tournament", - label: "Tournament", - active: @active, - icon: "fa-solid fa-ranking-star", - bsname: bsname, - url: Routes.ts_reports_report_path(@conn, :show, "tournament") - )} -<% end %> - <%= if allow?(@current_user, Teiserver.Account.LeaderboardReport.permissions()) do %> {central_component("section_menu_button", name: "leaderboard", diff --git a/lib/teiserver_web/templates/report/report/tournament.html.heex b/lib/teiserver_web/templates/report/report/tournament.html.heex deleted file mode 100644 index 61e8384b0..000000000 --- a/lib/teiserver_web/templates/report/report/tournament.html.heex +++ /dev/null @@ -1,295 +0,0 @@ -<% {_fg, _bg, bsname} = view_colour() |> colours() -user_moderator = allow?(@current_user, "Moderator") - -value_types = [ - "Leaderboard rating", - "Game rating", - "Skill value" -] %> - -{render( - TeiserverWeb.Report.GeneralView, - "sub_menu.html", - Map.merge(assigns, %{active: "reports"}) -)} - - - -
-
-
-
-
-   - Show/Hide help -
- {render( - TeiserverWeb.Report.ReportView, - "section_menu.html", - Map.merge(assigns, %{ - quick_search: "", - show_search: false, - active: "tournament" - }) - )} -

- -
- - -
-
- - Optionally use - #123 - (where 123 is their ID) to reference the ID of a user. -
- -
- - {select(:report, :game_type, @game_types, - class: "form-control", - selected: @params["game_type"] - )} -
- -
- - {select(:report, :value_type, value_types, - class: "form-control", - selected: @params["value_type"] - )} -
- - <%= if false and not Enum.empty?(@ratings) do %> -
- {central_component("checkbox", - name: "report[make_players]", - id: "report_make_players", - value: "true", - label: "Make players", - field: :see_group, - checked: false - )} -
- <% end %> - -
-  
- -
-
-
- -
- Win rate and game count both use the "Days since last active" value as a time period. -
- -
-
-
CSV data
- -
- -
-
Missing names
- -
- -
-
User but no rating
- -
- -
-
As IDs
- -
-
-
- - - -
-
-
- Found {Enum.count(@ratings)} of {Enum.count(@name_to_id_map)} players -
- - - - - - - - - - - - <%= if user_moderator do %> - - <% end %> - - - - <%= for {rating, pos} <- Enum.with_index(@ratings) do %> - - - - - - - - - - - - <%= if user_moderator do %> - - <% end %> - - <% end %> - -
 Player{@params["value_type"]}Moderation statusPlayer role 
- #{pos + 1} - - {central_component("icon", icon: rating.user.icon)} - {rating.user.name} - {case @params["value_type"] do - "Leaderboard rating" -> rating.leaderboard_rating |> round(2) - "Game rating" -> rating.rating_value |> round(2) - "Skill value" -> rating.skill |> round(2) - end} - - - - <%= if Enum.member?(rating.user.roles, "Tournament") do %> - - <% end %> - - "?filter=#{@params["game_type"]}"} - class="btn btn-sm btn-secondary" - > - Show - -
-
- -
- <.table id="users" rows={@team_data} table_class="table-sm table-hover"> - <:col :let={{team, _}} label="Team">{team + 1} - <:col :let={{_, ratings}} label="Captain"> - {ratings.captain_name} ({ratings.captain_rating}) - - - <:col :let={{_, ratings}} label="Size">{ratings.count} - <:col :let={{_, ratings}} label="Min">{ratings.min} - <:col :let={{_, ratings}} label="Max">{ratings.max} - - <:col :let={{_, ratings}} label="Mean">{ratings.mean} - <:col :let={{_, ratings}} label="Median">{ratings.median} - <:col :let={{_, ratings}} label="Standard Deviation">{ratings.stdev} - -
- -
- <% id_list_str = - @name_to_id_map - |> Map.values() - |> Enum.reject(&(&1 == nil)) - |> Enum.join(", ") %> -

Mass set-player snippet

- -
-
-
-
-
-
diff --git a/lib/teiserver_web/views/battle/general_view.ex b/lib/teiserver_web/views/battle/general_view.ex index 9592b554b..a18de82f6 100644 --- a/lib/teiserver_web/views/battle/general_view.ex +++ b/lib/teiserver_web/views/battle/general_view.ex @@ -13,5 +13,4 @@ defmodule TeiserverWeb.Battle.GeneralView do def view_colour("matches"), do: MatchLib.colours() def view_colour("ratings"), do: RatingLib.colours() def view_colour("parties"), do: PartyLib.colours() - def view_colour("tournaments"), do: :primary end diff --git a/priv/repo/migrations/20260324180447_remove_report_groups.exs b/priv/repo/migrations/20260324180447_remove_report_groups.exs new file mode 100644 index 000000000..ad604e935 --- /dev/null +++ b/priv/repo/migrations/20260324180447_remove_report_groups.exs @@ -0,0 +1,17 @@ +defmodule Teiserver.Repo.Migrations.RemoveReportGroups do + use Ecto.Migration + + def change do + alter table(:moderation_actions) do + remove :report_group_id + end + + alter table(:moderation_reports) do + remove :report_group_id + end + + drop table(:moderation_report_groups), mode: :cascade + drop table(:moderation_report_group_votes) + drop table(:moderation_report_group_messages) + end +end diff --git a/test/support/teiserver_test_lib.ex b/test/support/teiserver_test_lib.ex index 6f63f3f51..f692e73c9 100644 --- a/test/support/teiserver_test_lib.ex +++ b/test/support/teiserver_test_lib.ex @@ -453,7 +453,7 @@ defmodule Teiserver.TeiserverTestLib do }, params ) - |> Moderation.create_report_group_and_report() + |> Moderation.create_report() end def teiserver_seed do diff --git a/test/teiserver_web/live/microblog/post_form_component_test.exs b/test/teiserver_web/live/microblog/post_form_component_test.exs index f95909d5c..42ee99846 100644 --- a/test/teiserver_web/live/microblog/post_form_component_test.exs +++ b/test/teiserver_web/live/microblog/post_form_component_test.exs @@ -7,9 +7,12 @@ defmodule TeiserverWeb.Microblog.PostFormComponentTest do use TeiserverWeb.ConnCase - test "create_post works with or without poster_alias" do + setup do user = AccountFixtures.user_fixture() + %{user: user} + end + test "create_post works with or without poster_alias", %{user: user} do post_params = %{ "contents" => "test", "discord_channel_id" => "", @@ -35,9 +38,7 @@ defmodule TeiserverWeb.Microblog.PostFormComponentTest do assert result.poster_alias == "Jenny" end - test "Get discord text when discord id present" do - user = AccountFixtures.user_fixture() - + test "Get discord text when discord id present", %{user: user} do post_params = %{ "contents" => "test", "discord_channel_id" => "", @@ -61,9 +62,7 @@ defmodule TeiserverWeb.Microblog.PostFormComponentTest do assert String.contains?(result, "Posted by <@mydiscordname>") end - test "Get discord text when discord id not present and alias is present" do - user = AccountFixtures.user_fixture() - + test "Get discord text when discord id not present and alias is present", %{user: user} do post_params = %{ "contents" => "test", "discord_channel_id" => "", @@ -87,9 +86,7 @@ defmodule TeiserverWeb.Microblog.PostFormComponentTest do assert String.contains?(result, "Posted by contributoralias") end - test "Get discord text when discord id not present and alias not present" do - user = AccountFixtures.user_fixture() - + test "Get discord text when discord id not present and alias not present", %{user: user} do post_params = %{ "contents" => "test", "discord_channel_id" => "", diff --git a/test/teiserver_web/live/moderation/overwatch/index_test.exs b/test/teiserver_web/live/moderation/overwatch/index_test.exs deleted file mode 100644 index 9b018413f..000000000 --- a/test/teiserver_web/live/moderation/overwatch/index_test.exs +++ /dev/null @@ -1,61 +0,0 @@ -defmodule TeiserverWeb.Moderation.Overwatch.IndexLiveTest do - @moduledoc false - - alias Teiserver.Helpers.GeneralTestLib - alias Teiserver.TeiserverTestLib - - use TeiserverWeb.ConnCase, async: false - - import Phoenix.LiveViewTest - - defp auth_setup(_context) do - GeneralTestLib.conn_setup(TeiserverTestLib.overwatch_permissions()) - |> TeiserverTestLib.conn_setup() - end - - test "cannot access moderation overwatch without authenticating" do - {:ok, kw} = GeneralTestLib.conn_setup() - {:ok, conn} = Keyword.fetch(kw, :conn) - conn = get(conn, ~p"/moderation/overwatch") - assert redirected_to(conn) == ~p"/" - end - - describe "Index" do - setup [:auth_setup] - - test "index", %{conn: conn, user: conn_user} do - target_user = TeiserverTestLib.new_user() - - {:ok, _index_live, html} = live(conn, ~p"/moderation/overwatch") - - assert html =~ "Action status" - refute html =~ "#{target_user.name}" - - # Now make a new report - TeiserverTestLib.create_moderation_user_report(target_user.id, conn_user.id) - - {:ok, _index_live, html} = live(conn, ~p"/moderation/overwatch") - assert html =~ "Action status" - assert html =~ "#{target_user.name}" - end - end - - describe "Report group" do - setup [:auth_setup] - - test "show 1", %{conn: conn, user: conn_user} do - target_user = TeiserverTestLib.new_user() - - {:ok, rg, _report} = - TeiserverTestLib.create_moderation_user_report(target_user.id, conn_user.id, %{ - extra_text: "#{__MODULE__} extra text" - }) - - {:ok, _index_live, html} = live(conn, ~p"/moderation/overwatch/report_group/#{rg.id}") - assert html =~ "Report group for #{target_user.name}" - assert html =~ "#{__MODULE__} extra text" - assert html =~ "Reports (1)" - assert html =~ "Actions (0)" - end - end -end diff --git a/test/teiserver_web/live/moderation/report_user/index_test.exs b/test/teiserver_web/live/moderation/report_user/index_test.exs index ec1906837..a4be13e26 100644 --- a/test/teiserver_web/live/moderation/report_user/index_test.exs +++ b/test/teiserver_web/live/moderation/report_user/index_test.exs @@ -42,7 +42,6 @@ defmodule TeiserverWeb.Moderation.ReportUser.IndexLiveTest do user = TeiserverTestLib.new_user() # Ensure no existing groups - assert Moderation.list_report_groups(where: [target_id: user.id]) |> Enum.empty?() assert Moderation.list_reports(where: [target_id: user.id]) |> Enum.empty?() {:ok, index_live, html} = live(conn, ~p"/moderation/report_user/#{user.id}") @@ -97,9 +96,6 @@ defmodule TeiserverWeb.Moderation.ReportUser.IndexLiveTest do assert html =~ "Your report has been submitted" # Lets see if the report has come through! - [report_group] = Moderation.list_report_groups(where: [target_id: user.id]) - assert report_group.report_count == 1 - [report] = Moderation.list_reports(where: [target_id: user.id]) assert report.extra_text == "The extra text in my report" assert report.target_id == user.id