From 11971ebb0665d91af7ba2f4f6758fdf80119f73f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=90=D0=BB=D0=B5=D0=BA=D1=81=D0=B5=D0=B9=20=D0=A1=D0=B0?= =?UTF-8?q?=D0=B1=D0=B8=D0=BB=D0=B8=D0=BD?= Date: Tue, 5 May 2026 16:55:35 +0300 Subject: [PATCH] =?UTF-8?q?=D0=A0=D0=B5=D0=B0=D0=BB=D0=B8=D0=B7=D0=BE?= =?UTF-8?q?=D0=B2=D0=B0=D1=82=D1=8C=20=D0=BC=D0=BE=D0=B4=D1=83=D0=BB=D1=8C?= =?UTF-8?q?=20=D1=80=D0=B5=D0=B3=D0=B8=D1=81=D1=82=D1=80=D0=B0=D1=86=D0=B8?= =?UTF-8?q?=D0=B8=20=D0=B1=D0=BE=D1=82=D0=BE=D0=B2=20https://git.sabilin.c?= =?UTF-8?q?om/EventHub/EventHubBack/issues/19?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Makefile | 15 ++++++ src/core/core_user.erl | 43 +++++++++++++++++- src/infra/bot_controller.erl | 88 ++++++++++++++++++++++++++++++++++++ src/infra/infra_mnesia.erl | 1 + 4 files changed, 146 insertions(+), 1 deletion(-) create mode 100644 src/infra/bot_controller.erl diff --git a/Makefile b/Makefile index e8248b9..9777bff 100644 --- a/Makefile +++ b/Makefile @@ -307,6 +307,21 @@ docker-swarm-reg-admin: docker-swarm-check-admin: @docker exec $$(docker ps -qf "name=eventhub_eventhub" | head -n 1) /app/bin/eventhub eval 'eventhub_auth:authenticate_admin_request(<<"">>,<<"admin@eventhub.local">>,<<"123456">>).' +docker-swarm-reg-bots: + @docker exec $$(docker ps -qf "name=eventhub_eventhub" | head -n 1) /app/bin/eventhub eval "os:putenv(\"BOT_COUNT\", \"3000\"), bot_controller:register()." + +docker-swarm-reg-bots-stop: + @docker exec $$(docker ps -qf "name=eventhub_eventhub" | head -n 1) /app/bin/eventhub eval 'bot_controller:stop().' + +docker-swarm-delete-bots: + @docker exec $$(docker ps -qf "name=eventhub_eventhub" | head -n 1) /app/bin/eventhub eval 'bot_controller:delete().' + +docker-swarm-count-bots: + @for id in $$(docker ps -qf "name=eventhub_eventhub"); do \ + echo "=== $$id ==="; \ + docker exec $$id /app/bin/eventhub eval 'bot_controller:count_bots().'; \ + done + docker-swarm-shell: @docker exec $$(docker ps -qf "name=eventhub_eventhub" | head -n 1) /app/bin/eventhub remote_console diff --git a/src/core/core_user.erl b/src/core/core_user.erl index 17e818c..371afd5 100644 --- a/src/core/core_user.erl +++ b/src/core/core_user.erl @@ -7,6 +7,7 @@ -export([list_users/0]). -export([block/2, unblock/2]). -export([count_users/0, count_users_by_date/2]). +-export([create_bot/2, delete_bot/1]). %% Создание пользователя create(Email, Password) -> @@ -170,4 +171,44 @@ 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 -> U#user{role = Value}; set_field(status, Value, U) when Value =:= active; Value =:= frozen; Value =:= deleted -> U#user{status = Value}; -set_field(_, _, U) -> U. \ No newline at end of file +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 = generate_id(), + 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. \ No newline at end of file diff --git a/src/infra/bot_controller.erl b/src/infra/bot_controller.erl new file mode 100644 index 0000000..8e76645 --- /dev/null +++ b/src/infra/bot_controller.erl @@ -0,0 +1,88 @@ +-module(bot_controller). +-include("records.hrl"). + +-export([register/0, stop/0, delete/0, count_bots/0]). + +-define(REG_NAME, {bot_registration, node()}). + +%% ------------------------------------------------------------------ +%% @doc Асинхронно регистрирует ботов (одновременно только один процесс). +%% Возвращает pid процесса или ошибку. +%% ------------------------------------------------------------------ +register() -> + Count = list_to_integer(os:getenv("BOT_COUNT", "0")), + Domain = list_to_binary(os:getenv("BOT_DOMAIN", "eventhub.com")), + Password = list_to_binary(os:getenv("BOT_DEFAULT_PASSWORD", "botpass123")), + if Count =< 0 -> + {error, <<"BOT_COUNT must be greater than 0">>}; + true -> + io:format("Starting async registration of ~p bots with domain ~s...~n", [Count, Domain]), + Pid = spawn(fun() -> + register_bots(Count, Domain, Password) + end), + case global:register_name(?REG_NAME, Pid) of + yes -> + {ok, Pid}; + no -> + % Уже есть активный процесс регистрации + exit(Pid, kill), + {error, registration_already_running} + end + end. + +%% ------------------------------------------------------------------ +%% @doc Останавливает текущий процесс регистрации. +%% ------------------------------------------------------------------ +stop() -> + case global:whereis_name(?REG_NAME) of + undefined -> + io:format("No active bot registration found.~n"); + Pid -> + exit(Pid, kill), + io:format("Bot registration process ~p stopped.~n", [Pid]) + end. + +%% ------------------------------------------------------------------ +%% @doc Удаляет всех пользователей с ролью bot. +%% ------------------------------------------------------------------ +delete() -> + Bots = mnesia:dirty_match_object(#user{role = bot, _ = '_'}), + lists:foreach(fun(#user{id = Id}) -> + mnesia:dirty_delete({user, Id}) + end, Bots), + io:format("Deleted ~p bots.~n", [length(Bots)]), + ok. + +%% ------------------------------------------------------------------ +%% @doc Возвращает количество ботов в базе. +%% ------------------------------------------------------------------ +count_bots() -> + length(mnesia:dirty_match_object(#user{role = bot, _ = '_'})). + +%% ------------------------------------------------------------------ +%% Внутренние функции +%% ------------------------------------------------------------------ +register_bots(Count, Domain, Password) -> + register_bots(Count, Domain, Password, []). + +register_bots(0, _, _, Acc) -> + io:format("Async registration completed. Created ~p bots.~n", [length(Acc)]), + global:unregister_name(?REG_NAME); +register_bots(N, Domain, Password, Acc) -> + Email = generate_email(Domain), + case core_user:create_bot(Email, Password) of + {ok, _User} -> + register_bots(N-1, Domain, Password, [Email | Acc]); + {error, duplicate_email} -> + register_bots(N, Domain, Password, Acc); + {error, _} -> + register_bots(N-1, Domain, Password, Acc) + end. + +generate_email(Domain) -> + Name = random_string(8), + <<"bot_", Name/binary, "@", Domain/binary>>. + +random_string(Length) -> + Chars = <<"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789">>, + list_to_binary([lists:nth(rand:uniform(byte_size(Chars)), binary_to_list(Chars)) || _ <- lists:seq(1, Length)]). \ No newline at end of file diff --git a/src/infra/infra_mnesia.erl b/src/infra/infra_mnesia.erl index 5c93eeb..cedb5a9 100644 --- a/src/infra/infra_mnesia.erl +++ b/src/infra/infra_mnesia.erl @@ -242,6 +242,7 @@ create_indices() -> mnesia:add_table_index(calendar_specialist, calendar_id), mnesia:add_table_index(calendar_specialist, user_id), mnesia:add_table_index(user, nickname), + mnesia:add_table_index(user, email), mnesia:add_table_index(notification, user_id), mnesia:add_table_index(notification, is_read), ok. \ No newline at end of file