-module(core_user). -include("records.hrl"). -export([create/2, get_by_id/1, get_by_email/1, update/2, update_status/3, delete/1, update_last_login/1]). -export([email_exists/1]). -export([list_users/0]). -export([block/2, unblock/2]). -export([count_users/0, count_users_by_date/2, list_all/0]). -export([create_bot/2, delete_bot/1]). %% Создание пользователя create(Email, Password) -> % Проверяем, существует ли email case email_exists(Email) of true -> {error, email_exists}; false -> Id = infra_utils:generate_id(16), {ok, PasswordHash} = logic_auth:hash_password(Password), User = #user{ id = Id, email = Email, password_hash = PasswordHash, role = user, status = active, created_at = calendar:universal_time(), updated_at = calendar:universal_time() }, F = fun() -> mnesia:write(User), {ok, User} end, case mnesia:transaction(F) of {atomic, Result} -> Result; {aborted, Reason} -> {error, Reason} end end. %% Получение пользователя по ID get_by_id(Id) -> case mnesia:dirty_read(user, Id) of [] -> {error, not_found}; [User] -> {ok, User} end. %% Получение пользователя по email get_by_email(Email) -> Match = #user{email = Email, _ = '_'}, case mnesia:dirty_match_object(Match) of [] -> {error, not_found}; [User] -> {ok, User} end. %% Проверка существования email email_exists(Email) -> case get_by_email(Email) of {ok, _} -> true; {error, _} -> false end. %% Обновление пользователя update(Id, Updates) -> F = fun() -> case mnesia:read(user, Id) of [] -> {error, not_found}; [User] -> UpdatedUser = apply_updates(User, Updates), mnesia:write(UpdatedUser), {ok, UpdatedUser} end end, case mnesia:transaction(F) of {atomic, Result} -> Result; {aborted, Reason} -> {error, Reason} end. update_last_login(Id) -> case get_by_id(Id) of {ok, User} -> Updated = User#user{last_login = calendar:universal_time()}, mnesia:dirty_write(Updated), {ok, Updated}; Error -> Error end. update_status(Id, Status, Reason) -> case get_by_id(Id) of {ok, User} -> Updated = User#user{status = Status, reason = Reason, updated_at = calendar:universal_time()}, mnesia:dirty_write(Updated), {ok, Updated}; Error -> Error end. %% Удаление пользователя (soft delete) delete(Id) -> update(Id, [{status, deleted}]). list_users() -> Users = mnesia:dirty_match_object(#user{_ = '_'}), ActiveUsers = [U || U <- Users, U#user.status =/= deleted], {ok, [user_to_map(U) || U <- ActiveUsers]}. user_to_map(User) when is_map(User) -> #{ id => maps:get(id, User), email => maps:get(email, User), role => maps:get(role, User, <<"user">>), status => maps:get(status, User, <<"active">>), reason => maps:get(reason, User, undefined), created_at => maps:get(created_at, User), updated_at => maps:get(updated_at, User) }; user_to_map(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 => User#user.created_at, updated_at => User#user.updated_at }. block(Id, Reason) -> case get_by_id(Id) of {ok, User} -> Updated = User#user{status = blocked, reason = Reason, updated_at = calendar:universal_time()}, mnesia:dirty_write(Updated), {ok, Updated}; Error -> Error end. unblock(Id, Reason) -> case get_by_id(Id) of {ok, User} -> Updated = User#user{status = active, reason = Reason, updated_at = calendar:universal_time()}, mnesia:dirty_write(Updated), {ok, Updated}; Error -> Error end. count_users() -> mnesia:table_info(user, size). %% Административный список (все пользователи, без фильтрации) list_all() -> mnesia:dirty_match_object(#user{_ = '_'}). count_users_by_date(From, To) -> All = mnesia:dirty_match_object(#user{_ = '_'}), Filtered = lists:filter(fun(U) -> U#user.created_at >= From andalso U#user.created_at =< To end, All), Counts = lists:foldl(fun(U, Acc) -> Day = date_part(U#user.created_at), case lists:keyfind(Day, 1, Acc) of false -> [{Day, 1} | Acc]; {Day, C} -> lists:keyreplace(Day, 1, Acc, {Day, C+1}) end end, [], Filtered), lists:sort(Counts). date_part({{Y,M,D}, _}) -> {Y,M,D}. apply_updates(User, Updates) -> Updated = lists:foldl(fun({Field, Value}, U) -> set_field(Field, Value, U) end, User, Updates), Updated#user{updated_at = calendar:universal_time()}. set_field(email, Value, U) -> U#user{email = Value}; set_field(password_hash, Value, U) -> U#user{password_hash = Value}; set_field(role, Value, U) when Value =:= user; Value =:= admin; Value =:= bot -> U#user{role = Value}; set_field(status, Value, U) when Value =:= active; Value =:= frozen; Value =:= deleted -> U#user{status = Value}; set_field(reason, Value, U) -> U#user{reason = Value}; set_field(nickname, Value, U) -> U#user{nickname = Value}; set_field(avatar_url, Value, U) -> U#user{avatar_url = Value}; set_field(timezone, Value, U) -> U#user{timezone = Value}; set_field(language, Value, U) -> U#user{language = Value}; set_field(social_links, Value, U) -> U#user{social_links = Value}; set_field(phone, Value, U) -> U#user{phone = Value}; set_field(preferences, Value, U) -> U#user{preferences = Value}; set_field(_, _, U) -> U. %% ------------------------------------------------------------------ %% API для ботов %% ------------------------------------------------------------------ -spec create_bot(Email :: binary(), Password :: binary()) -> {ok, User :: #user{}} | {error, duplicate_email | invalid_email}. create_bot(Email, Password) -> case email_exists(Email) of true -> {error, email_exists}; false -> case mnesia:dirty_index_read(user, Email, email) of [] -> {ok, PasswordHash} = logic_auth:hash_password(Password), Id = infra_utils:generate_id(16), User = #user{ id = Id, email = Email, password_hash = PasswordHash, role = bot, status = active, created_at = calendar:universal_time(), updated_at = calendar:universal_time() }, ok = mnesia:dirty_write(User), {ok, User}; _ -> {error, duplicate_email} end end. -spec delete_bot(Id :: binary()) -> ok | {error, not_found}. delete_bot(Id) -> case mnesia:dirty_read({user, Id}) of [#user{role = bot}] -> mnesia:dirty_delete({user, Id}), ok; [] -> {error, not_found} end.