177 lines
6.3 KiB
Erlang
177 lines
6.3 KiB
Erlang
%%%-------------------------------------------------------------------
|
|
%%% @doc Административный обработчик бан-слов.
|
|
%%% GET – список всех слов с пагинацией.
|
|
%%% POST – добавить новое слово.
|
|
%%% DELETE – удалить слово по :word.
|
|
%%% @end
|
|
%%%-------------------------------------------------------------------
|
|
-module(admin_handler_banned_words).
|
|
-behaviour(cowboy_handler).
|
|
|
|
-export([init/2]).
|
|
-export([trails/0]).
|
|
|
|
-include("records.hrl").
|
|
|
|
-spec init(cowboy_req:req(), any()) -> {ok, cowboy_req:req(), any()}.
|
|
init(Req, _Opts) ->
|
|
case cowboy_req:method(Req) of
|
|
<<"GET">> -> list_words(Req);
|
|
<<"POST">> -> add_word(Req);
|
|
<<"DELETE">> -> delete_word(Req);
|
|
_ -> handler_utils:send_error(Req, 405, <<"Method not allowed">>)
|
|
end.
|
|
|
|
-spec trails() -> [map()].
|
|
trails() ->
|
|
[
|
|
#{ % GET list
|
|
path => <<"/v1/admin/banned-words">>,
|
|
method => <<"GET">>,
|
|
description => <<"List all banned words (admin)">>,
|
|
tags => [<<"Banned Words">>],
|
|
parameters => [
|
|
#{name => <<"limit">>, in => <<"query">>, schema => #{type => integer}, description => <<"Page size">>},
|
|
#{name => <<"offset">>, in => <<"query">>, schema => #{type => integer}, description => <<"Offset">>}
|
|
],
|
|
responses => #{
|
|
200 => #{
|
|
description => <<"Array of banned words">>,
|
|
content => #{<<"application/json">> => #{schema => #{
|
|
type => array,
|
|
items => banned_word_schema()
|
|
}}}
|
|
}
|
|
}
|
|
},
|
|
#{ % POST add
|
|
path => <<"/v1/admin/banned-words">>,
|
|
method => <<"POST">>,
|
|
description => <<"Add a new banned word">>,
|
|
tags => [<<"Banned Words">>],
|
|
requestBody => #{
|
|
required => true,
|
|
content => #{<<"application/json">> => #{schema => #{
|
|
type => object,
|
|
required => [<<"word">>],
|
|
properties => #{
|
|
word => #{type => string}
|
|
}
|
|
}}}
|
|
},
|
|
responses => #{
|
|
201 => #{description => <<"Word added">>},
|
|
409 => #{description => <<"Word already exists">>}
|
|
}
|
|
},
|
|
#{ % DELETE by word
|
|
path => <<"/v1/admin/banned-words/:word">>,
|
|
method => <<"DELETE">>,
|
|
description => <<"Remove a banned word">>,
|
|
tags => [<<"Banned Words">>],
|
|
parameters => [
|
|
#{
|
|
name => <<"word">>,
|
|
in => <<"path">>,
|
|
description => <<"The word to remove">>,
|
|
required => true,
|
|
schema => #{type => string}
|
|
}
|
|
],
|
|
responses => #{
|
|
200 => #{description => <<"Word removed">>},
|
|
404 => #{description => <<"Word not found">>}
|
|
}
|
|
}
|
|
].
|
|
|
|
banned_word_schema() ->
|
|
#{
|
|
type => object,
|
|
properties => #{
|
|
id => #{type => string},
|
|
word => #{type => string},
|
|
added_by => #{type => string, nullable => true},
|
|
added_at => #{type => string, format => <<"date-time">>, nullable => true}
|
|
}
|
|
}.
|
|
|
|
%%% Internal functions
|
|
|
|
list_words(Req) ->
|
|
case handler_utils:auth_admin(Req) of
|
|
{ok, _AdminId, Req1} ->
|
|
Pagination = handler_utils:parse_pagination_params(Req1),
|
|
%% core_banned_words:list_banned_words() возвращает список, а не {ok, List}
|
|
AllWords = core_banned_words:list_banned_words(),
|
|
BannedWords = lists:sort(AllWords),
|
|
Total = length(BannedWords),
|
|
Page = lists:sublist(BannedWords, maps:get(offset, Pagination) + 1, maps:get(limit, Pagination)),
|
|
Json = [word_to_map(W) || W <- Page],
|
|
ExtraHeaders = pagination_headers(Pagination, Total),
|
|
handler_utils:send_json(Req1, 200, Json, ExtraHeaders);
|
|
{error, Code, Msg, Req1} ->
|
|
handler_utils:send_error(Req1, Code, Msg)
|
|
end.
|
|
|
|
add_word(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
|
|
#{<<"word">> := Word} ->
|
|
case core_banned_words:add_banned_word(Word, AdminId) of
|
|
{ok, _} ->
|
|
handler_utils:send_json(Req2, 201, #{status => <<"added">>});
|
|
{error, already_exists} ->
|
|
handler_utils:send_error(Req2, 409, <<"Word already exists">>);
|
|
{error, _} ->
|
|
handler_utils:send_error(Req2, 500, <<"Internal server error">>)
|
|
end;
|
|
_ ->
|
|
handler_utils:send_error(Req2, 400, <<"Missing 'word' field">>)
|
|
catch
|
|
_:_ -> handler_utils:send_error(Req1, 400, <<"Invalid JSON">>)
|
|
end;
|
|
{error, Code, Msg, Req1} ->
|
|
handler_utils:send_error(Req1, Code, Msg)
|
|
end.
|
|
|
|
delete_word(Req) ->
|
|
case handler_utils:auth_admin(Req) of
|
|
{ok, _AdminId, Req1} ->
|
|
Word = cowboy_req:binding(word, Req1),
|
|
case core_banned_words:remove_banned_word(Word) of
|
|
{ok, _} ->
|
|
handler_utils:send_json(Req1, 200, #{status => <<"deleted">>});
|
|
{error, not_found} ->
|
|
handler_utils:send_error(Req1, 404, <<"Word not found">>);
|
|
{error, _} ->
|
|
handler_utils:send_error(Req1, 500, <<"Internal server error">>)
|
|
end;
|
|
{error, Code, Msg, Req1} ->
|
|
handler_utils:send_error(Req1, Code, Msg)
|
|
end.
|
|
|
|
%% @private Преобразование записи banned_word в JSON-совместимую карту.
|
|
word_to_map(W) ->
|
|
#{
|
|
id => W#banned_word.id,
|
|
word => W#banned_word.word,
|
|
added_by => W#banned_word.added_by,
|
|
added_at => datetime_to_iso8601(W#banned_word.added_at)
|
|
}.
|
|
|
|
%% @private Форматирование datetime в ISO8601 строку.
|
|
datetime_to_iso8601(undefined) -> undefined;
|
|
datetime_to_iso8601({{Year, Month, Day}, {Hour, Minute, Second}}) ->
|
|
iolist_to_binary(io_lib:format("~4..0B-~2..0B-~2..0BT~2..0B:~2..0B:~2..0BZ",
|
|
[Year, Month, Day, Hour, Minute, Second])).
|
|
|
|
pagination_headers(#{limit := Limit, offset := Offset}, Total) ->
|
|
RangeEnd = min(Offset + Limit - 1, Total - 1),
|
|
#{
|
|
<<"content-range">> => iolist_to_binary(io_lib:format("items ~B-~B/~B", [Offset, RangeEnd, Total])),
|
|
<<"x-total-count">> => integer_to_binary(Total),
|
|
<<"access-control-expose-headers">> => <<"Content-Range, X-Total-Count">>
|
|
}. |