-module(admin_handler_user_by_id). -include("records.hrl"). -export([init/2]). init(Req, _Opts) -> case cowboy_req:method(Req) of <<"GET">> -> get_user(Req); <<"PUT">> -> update_user(Req); <<"DELETE">> -> delete_user(Req); _ -> send_error(Req, 405, <<"Method not allowed">>) end. get_user(Req) -> case handler_auth:authenticate(Req) of {ok, AdminId, Req1} -> case admin_utils:is_admin(AdminId) of true -> UserId = cowboy_req:binding(id, Req1), case core_user:get_by_id(UserId) of {ok, User} -> send_json(Req1, 200, user_to_json(User)); {error, not_found} -> send_error(Req1, 404, <<"User not found">>) end; false -> send_error(Req1, 403, <<"Admin access required">>) end; {error, Code, Message, Req1} -> send_error(Req1, Code, Message) end. update_user(Req) -> case handler_auth:authenticate(Req) of {ok, AdminId, Req1} -> case admin_utils:is_admin(AdminId) of true -> UserId = cowboy_req:binding(id, Req1), {ok, Body, Req2} = cowboy_req:read_body(Req1), try jsx:decode(Body, [return_maps]) of Updates when map_size(Updates) > 0 -> % Проверка на наличие reason при изменении статуса case maps:find(<<"status">>, Updates) of {ok, NewStatus} when NewStatus =:= <<"blocked">> orelse NewStatus =:= <<"active">> -> case maps:find(<<"reason">>, Updates) of {ok, Reason} when byte_size(Reason) > 0 -> apply_updates(UserId, Updates, AdminId, Reason, Req2); _ -> send_error(Req2, 400, <<"Missing or empty reason">>) end; _ -> apply_updates(UserId, Updates, AdminId, undefined, Req2) end; _ -> send_error(Req2, 400, <<"Request body is empty">>) catch _:_ -> send_error(Req2, 400, <<"Invalid JSON">>) end; false -> send_error(Req1, 403, <<"Admin access required">>) end; {error, Code, Message, Req1} -> send_error(Req1, Code, Message) end. delete_user(Req) -> case handler_auth:authenticate(Req) of {ok, AdminId, Req1} -> case admin_utils:is_admin(AdminId) of true -> UserId = cowboy_req:binding(id, Req1), case core_user:delete(UserId) of {ok, _} -> send_json(Req1, 200, #{status => <<"deleted">>}); {error, not_found} -> send_error(Req1, 404, <<"User not found">>) end; false -> send_error(Req1, 403, <<"Admin access required">>) end; {error, Code, Message, Req1} -> send_error(Req1, Code, Message) end. %% ── Вспомогательная функция обновления ──────────────────── apply_updates(UserId, Updates, AdminId, Reason, Req) -> Converted = convert_updates(maps:to_list(Updates)), case core_user:update(UserId, Converted) of {ok, User} -> % Логируем, если был указан reason case Reason of undefined -> ok; _ -> case core_admin:get_by_id(AdminId) of {ok, Admin} -> Action = case maps:get(<<"status">>, Updates, undefined) of <<"blocked">> -> <<"block_user">>; <<"active">> -> <<"unblock_user">>; _ -> <<"update_user">> end, core_admin_audit:log(AdminId, Admin#admin.email, Admin#admin.role, Action, <<"user">>, UserId, <<"127.0.0.1">>, Reason); _ -> ok end end, send_json(Req, 200, user_to_json(User)); {error, not_found} -> send_error(Req, 404, <<"User not found">>); {error, _} -> send_error(Req, 500, <<"Internal server error">>) end. convert_updates(Updates) -> lists:map(fun({<<"status">>, Value}) -> {status, binary_to_existing_atom(Value, utf8)}; ({<<"role">>, Value}) -> {role, binary_to_existing_atom(Value, utf8)}; ({<<"reason">>, Value}) -> {reason, Value}; (Other) -> Other end, Updates). user_to_json(User) -> #{ id => User#user.id, email => User#user.email, role => atom_to_binary(User#user.role, utf8), status => atom_to_binary(User#user.status, utf8), reason => User#user.reason, created_at => datetime_to_iso8601(User#user.created_at), updated_at => datetime_to_iso8601(User#user.updated_at) }. datetime_to_iso8601({{Y,M,D},{H,Min,S}}) -> iolist_to_binary(io_lib:format("~4..0B-~2..0B-~2..0BT~2..0B:~2..0B:~2..0BZ", [Y,M,D,H,Min,S])); datetime_to_iso8601(_) -> null. send_json(Req, Status, Data) -> Headers = #{ <<"content-type">> => <<"application/json">>, <<"access-control-allow-origin">> => <<"*">>, <<"access-control-expose-headers">> => <<"Content-Range">> }, Body = jsx:encode(Data), cowboy_req:reply(Status, Headers, Body, Req), {ok, Body, []}. send_error(Req, Code, Message) -> Headers = #{ <<"content-type">> => <<"application/json">>, <<"access-control-allow-origin">> => <<"*">>, <<"access-control-expose-headers">> => <<"Content-Range">> }, Body = jsx:encode(#{error => Message}), cowboy_req:reply(Code, Headers, Body, Req), {ok, Body, []}.