Добавить автомодерацию: политики, порог жалоб, settings/hits API и тесты.
Refs EventHub/EventHubBack#37 Refs EventHub/EventHubBack#38 Refs EventHub/EventHubBack#39 Refs EventHub/EventHubBack#40
This commit is contained in:
@@ -0,0 +1,162 @@
|
||||
%%%-------------------------------------------------------------------
|
||||
%%% Admin: настройки автомодерации и очередь hits.
|
||||
%%%-------------------------------------------------------------------
|
||||
-module(admin_handler_automod).
|
||||
-behaviour(cowboy_handler).
|
||||
-export([init/2, trails/0]).
|
||||
-include("records.hrl").
|
||||
|
||||
init(Req, _Opts) ->
|
||||
Path = cowboy_req:path(Req),
|
||||
Method = cowboy_req:method(Req),
|
||||
case {Method, Path} of
|
||||
{<<"GET">>, <<"/v1/admin/automod/settings">>} -> get_settings(Req);
|
||||
{<<"PUT">>, <<"/v1/admin/automod/settings">>} -> put_settings(Req);
|
||||
{<<"GET">>, <<"/v1/admin/automod/hits">>} -> list_hits(Req);
|
||||
{<<"PUT">>, _} ->
|
||||
case cowboy_req:binding(id, Req) of
|
||||
undefined -> handler_utils:send_error(Req, 404, <<"Not found">>);
|
||||
Id -> resolve_hit(Req, Id)
|
||||
end;
|
||||
_ ->
|
||||
handler_utils:send_error(Req, 405, <<"Method not allowed">>)
|
||||
end.
|
||||
|
||||
trails() ->
|
||||
[
|
||||
#{path => <<"/v1/admin/automod/settings">>,
|
||||
method => <<"GET">>,
|
||||
description => <<"Get automoderation settings">>,
|
||||
tags => [<<"Automoderation">>],
|
||||
responses => #{200 => #{description => <<"Settings">>}}},
|
||||
#{path => <<"/v1/admin/automod/settings">>,
|
||||
method => <<"PUT">>,
|
||||
description => <<"Update automoderation settings">>,
|
||||
tags => [<<"Automoderation">>],
|
||||
responses => #{200 => #{description => <<"Updated">>}, 403 => #{description => <<"Forbidden">>}}},
|
||||
#{path => <<"/v1/admin/automod/hits">>,
|
||||
method => <<"GET">>,
|
||||
description => <<"List automoderation hits">>,
|
||||
tags => [<<"Automoderation">>],
|
||||
responses => #{200 => #{description => <<"Hits">>}}},
|
||||
#{path => <<"/v1/admin/automod/hits/{id}">>,
|
||||
method => <<"PUT">>,
|
||||
description => <<"Approve or reject an automod hit">>,
|
||||
tags => [<<"Automoderation">>],
|
||||
responses => #{200 => #{description => <<"Resolved">>}, 404 => #{description => <<"Not found">>}}}
|
||||
].
|
||||
|
||||
get_settings(Req) ->
|
||||
case handler_utils:auth_admin(Req) of
|
||||
{ok, AdminId, Req1} ->
|
||||
case admin_utils:check_role(AdminId, [superadmin, admin, moderator]) of
|
||||
true ->
|
||||
{ok, S} = logic_automoderation:get_settings(),
|
||||
handler_utils:send_json(Req1, 200, logic_automoderation:settings_to_json(S));
|
||||
false ->
|
||||
handler_utils:send_error(Req1, 403, <<"Access denied">>)
|
||||
end;
|
||||
{error, Code, Msg, Req1} ->
|
||||
handler_utils:send_error(Req1, Code, Msg)
|
||||
end.
|
||||
|
||||
put_settings(Req) ->
|
||||
case handler_utils:auth_admin(Req) of
|
||||
{ok, AdminId, Req1} ->
|
||||
{ok, Body, Req2} = cowboy_req:read_body(Req1),
|
||||
try jsx:decode(Body, [return_maps]) of
|
||||
Map when is_map(Map) ->
|
||||
Updates = parse_settings(Map),
|
||||
case logic_automoderation:update_settings(AdminId, Updates) of
|
||||
{ok, S} ->
|
||||
admin_utils:log_admin_action(AdminId, <<"update">>, <<"automod_settings">>,
|
||||
<<"default">>, <<>>, Req2),
|
||||
handler_utils:send_json(Req2, 200, logic_automoderation:settings_to_json(S));
|
||||
{error, access_denied} ->
|
||||
handler_utils:send_error(Req2, 403, <<"Access denied">>);
|
||||
{error, {invalid_field, _}} ->
|
||||
handler_utils:send_error(Req2, 400, <<"Invalid settings">>);
|
||||
{error, _} ->
|
||||
handler_utils:send_error(Req2, 500, <<"Internal server error">>)
|
||||
end;
|
||||
_ ->
|
||||
handler_utils:send_error(Req2, 400, <<"Invalid JSON">>)
|
||||
catch
|
||||
_:_ -> handler_utils:send_error(Req1, 400, <<"Invalid JSON format">>)
|
||||
end;
|
||||
{error, Code, Msg, Req1} ->
|
||||
handler_utils:send_error(Req1, Code, Msg)
|
||||
end.
|
||||
|
||||
list_hits(Req) ->
|
||||
case handler_utils:auth_admin(Req) of
|
||||
{ok, AdminId, Req1} ->
|
||||
Qs = cowboy_req:parse_qs(Req1),
|
||||
Filters = lists:filtermap(fun
|
||||
({<<"status">>, <<"open">>}) -> {true, {status, open}};
|
||||
({<<"status">>, <<"approved">>}) -> {true, {status, approved}};
|
||||
({<<"status">>, <<"rejected">>}) -> {true, {status, rejected}};
|
||||
({<<"trigger">>, <<"keyword">>}) -> {true, {trigger, keyword}};
|
||||
({<<"trigger">>, <<"report_threshold">>}) -> {true, {trigger, report_threshold}};
|
||||
(_) -> false
|
||||
end, Qs),
|
||||
case logic_automoderation:list_hits(AdminId, Filters) of
|
||||
{ok, Hits} ->
|
||||
Sorted = lists:reverse(lists:keysort(#automod_hit.created_at, Hits)),
|
||||
handler_utils:send_json(Req1, 200, [logic_automoderation:hit_to_json(H) || H <- Sorted]);
|
||||
{error, access_denied} ->
|
||||
handler_utils:send_error(Req1, 403, <<"Access denied">>)
|
||||
end;
|
||||
{error, Code, Msg, Req1} ->
|
||||
handler_utils:send_error(Req1, Code, Msg)
|
||||
end.
|
||||
|
||||
resolve_hit(Req, Id) ->
|
||||
case handler_utils:auth_admin(Req) of
|
||||
{ok, AdminId, Req1} ->
|
||||
{ok, Body, Req2} = cowboy_req:read_body(Req1),
|
||||
try jsx:decode(Body, [return_maps]) of
|
||||
#{<<"status">> := StatusBin} ->
|
||||
Decision = case StatusBin of
|
||||
<<"approved">> -> approved;
|
||||
<<"rejected">> -> rejected;
|
||||
_ -> invalid
|
||||
end,
|
||||
case Decision of
|
||||
invalid ->
|
||||
handler_utils:send_error(Req2, 400, <<"status must be approved or rejected">>);
|
||||
_ ->
|
||||
case logic_automoderation:resolve_hit(AdminId, Id, Decision) of
|
||||
{ok, Hit} ->
|
||||
admin_utils:log_admin_action(AdminId, StatusBin, <<"automod_hit">>, Id, <<>>, Req2),
|
||||
handler_utils:send_json(Req2, 200, logic_automoderation:hit_to_json(Hit));
|
||||
{error, not_found} ->
|
||||
handler_utils:send_error(Req2, 404, <<"Hit not found">>);
|
||||
{error, already_resolved} ->
|
||||
handler_utils:send_error(Req2, 409, <<"Already resolved">>);
|
||||
{error, access_denied} ->
|
||||
handler_utils:send_error(Req2, 403, <<"Access denied">>);
|
||||
{error, _} ->
|
||||
handler_utils:send_error(Req2, 500, <<"Internal server error">>)
|
||||
end
|
||||
end;
|
||||
_ ->
|
||||
handler_utils:send_error(Req2, 400, <<"Missing status">>)
|
||||
catch
|
||||
_:_ -> handler_utils:send_error(Req1, 400, <<"Invalid JSON format">>)
|
||||
end;
|
||||
{error, Code, Msg, Req1} ->
|
||||
handler_utils:send_error(Req1, Code, Msg)
|
||||
end.
|
||||
|
||||
parse_settings(Map) ->
|
||||
lists:filtermap(fun
|
||||
({<<"keyword_action">>, <<"reject">>}) -> {true, {keyword_action, reject}};
|
||||
({<<"keyword_action">>, <<"flag">>}) -> {true, {keyword_action, flag}};
|
||||
({<<"keyword_action">>, <<"censor">>}) -> {true, {keyword_action, censor}};
|
||||
({<<"match_mode">>, <<"word_boundary">>}) -> {true, {match_mode, word_boundary}};
|
||||
({<<"match_mode">>, <<"substring">>}) -> {true, {match_mode, substring}};
|
||||
({<<"report_threshold">>, V}) when is_integer(V) ->
|
||||
{true, {report_threshold, V}};
|
||||
(_) -> false
|
||||
end, maps:to_list(Map)).
|
||||
Reference in New Issue
Block a user