diff --git a/.gitignore b/.gitignore index 06a50614f0b12ea9d0a0c4ba100b297f93d7a72b..b745ba1930875203f80726c2665b5e0a9ea9cc15 100644 --- a/.gitignore +++ b/.gitignore @@ -17,6 +17,10 @@ workspace.xml .idea/ .DS_Store _build +*.crashdump + +# Generated code +priv/ # Excessive for iOS models apps/roster/priv/macbert/Model/PublishService.swift @@ -54,3 +58,6 @@ apps/roster/priv/macbert/Spec/Presence_Spec.swift apps/roster/priv/macbert/Spec/cx_Spec.swift apps/roster/priv/macbert/Spec/load_Spec.swift apps/service/priv/ +admin/admin +etc/etc +asserts/asserts diff --git a/Makefile b/Makefile index dd4a9409d9f4017b9c71f5525a81e6935bd932d9..6f847022a94ce544b7e07843b887ebb281ca2bb5 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,63 @@ +REBAR ?= ./rebar3 + RELEASE := mq COOKIE := node_runner VER := 1.0.0 -default: deps compile +all: compile + +compile: + @$(REBAR) compile + +clean: + @rm -rf priv + @$(REBAR) clean + +eunit: + @$(REBAR) eunit + + +export NYNJA_HOST ?= 127.0.0.1 +# Start and test a local release +# Use -$(REBAR) to enable cleanup when tests failed +# Need to manually `make local-build` to save time +rel-test: + ./_build/local/rel/server/bin/server start + sleep 15 + -$(REBAR) ct --dir test --spec test/integration_test.ts + ./_build/local/rel/server/bin/server stop + +console: local-build + @./_build/local/rel/server/bin/server console + +start: local-build + @./_build/local/rel/server/bin/server start + +attach: + @./_build/local/rel/server/bin/server attach + +local-build: + @$(REBAR) as local release + + +prod-build: + @$(REBAR) as prod release + +prod-console: prod-build + @./_build/prod/rel/server/bin/server console + +prod-start: prod-build + @./_build/prod/rel/server/bin/server start + +prod-attach: + @./_build/prod/rel/server/bin/server attach + +tar: + @$(REBAR) as prod tar + -include otp.mk +.PHONY: \ + all compile clean eunit rel-test local-build \ + start console attach \ + prod-build prod-console prod-start prod-attach \ + tar diff --git a/README.md b/README.md index 18a1a741506bbfed6cb608cf522a8ab76623bdfc..67ffd9a74ea7959f9f35874b17161d59152abcd3 100644 --- a/README.md +++ b/README.md @@ -26,79 +26,26 @@ Developers Setup ---------------- ``` -$ curl -fsSL https://raw.github.com/synrc/mad/master/mad > mad \ - && chmod +x mad \ - && sudo cp /usr/local/bin -$ mad dep com rep -Configuration: [{n2o, - [{port,8000}, - {app,review}, - {pickler,n2o_secret}, - {formatter,bert}, - {log_modules,config}, - {log_level,config}]}, - {emq_dashboard, - [{listeners_dash, - [{http,18083,[{acceptors,4},{max_clients,512}]}]}]}, - {emq_modules, - [{modules, - [{emq_mod_presence,[{qos,1}]}, - {emq_mod_subscription,[{<<"%u/%c/#">>,2}]}, - {emq_mod_rewrite, - [{rewrite,"x/#","^x/y/(.+)$","z/y/$1"}, - {rewrite,"y/+/z/#","^y/(.+)/z/(.+)$", - "y/z/$2"}]}]}]}, - {emqttd, - [{listeners, - [{http,8083,[{acceptors,4},{max_clients,512}]}, - {tcp,1883,[{acceptors,4},{max_clients,512}]}]}, - {sysmon, - [{long_gc,false}, - {long_schedule,240}, - {large_heap,8000000}, - {busy_port,false}, - {busy_dist_port,true}]}, - {session, - [{upgrade_qos,off}, - {max_inflight,32}, - {retry_interval,20}, - {max_awaiting_rel,100}, - {await_rel_timeout,20}, - {enable_stats,off}]}, - {queue,[]}, - {allow_anonymous,true}, - {protocol, - [{max_clientid_len,1024},{max_packet_size,64000}]}, - {acl_file,"etc/acl.conf"}, - {plugins_etc_dir,"etc/plugins/"}, - {plugins_loaded_file,"etc/loaded_plugins"}, - {pubsub, - [{pool_size,8},{by_clientid,true},{async,true}]}]}, - {kvs, - [{dba,store_mnesia}, - {schema,[kvs_user,kvs_acl,kvs_feed,kvs_subscription]}]}] -Applications: [kernel,stdlib,gproc,lager_syslog,pbkdf2,asn1,fs,ranch,mnesia, - compiler,inets,crypto,syntax_tools,xmerl,gen_logger,esockd, - cowlib,goldrush,public_key,lager,ssl,cowboy,mochiweb,emqttd, - erlydtl,kvs,mad,emqttc,nitro,rest,sh,syslog,review] -Erlang/OTP 19 [erts-8.3] [source] [64-bit] [smp:4:4] - [async-threads:10] [hipe] [kernel-poll:false] [dtrace] - -Eshell V8.3 (abort with ^G) -starting emqttd on node 'nonode@nohost' -Nonexistent: [] -Plugins: [{mqtt_plugin,emq_auth_username,"2.1.1", - "Authentication with Username/Password",false}, - {mqtt_plugin,emq_dashboard,"2.1.1","EMQ Web Dashboard",false}, - {mqtt_plugin,emq_modules,"2.1.1","EMQ Modules",false}, - {mqtt_plugin,n2o,"4.5-mqtt","N2O Server",false}] -Names: [emq_dashboard,n2o] -dashboard:http listen on 0.0.0.0:18083 with 4 acceptors. -Async Start Attempt {handler,"timer",n2o,system,n2o,[],[]} -Proc Init: init -mqtt:ws listen on 0.0.0.0:8083 with 4 acceptors. -mqtt:tcp listen on 0.0.0.0:1883 with 4 acceptors. -emqttd 2.1.1 is running now +$ make +===> Verifying dependencies... +... +===> Compiling roster +===> Compiling service +$ make console +===> Verifying dependencies... +===> Compiling roster +===> Compiling service +===> Starting relx build process ... +===> Resolving OTP Applications from directories: +... +===> release successfully created! +Exec: /.../erlang/20.3/erts-9.3/bin/erlexec -boot /.../nynja/server/_build/local/rel/server/releases/1.0.0/server -mode embedded -boot_var ERTS_LIB_DIR /.../erlang/20.3/lib -config /.../nynja/server/_build/local/rel/server/releases/1.0.0/sys.config -args_file /.../nynja/server/_build/local/rel/server/releases/1.0.0/vm.args -- console +Root: /.../nynja/server/_build/local/rel/server +/.../nynja/server/_build/local/rel/server +Erlang/OTP 20 [erts-9.3] [source] [64-bit] [smp:8:8] [ds:8:8:10] [async-threads:32] [hipe] [kernel-poll:true] +... +Eshell V9.3 (abort with ^G) +(mq@127.0.0.1)1> > ``` @@ -132,8 +79,7 @@ Creating Single File Bundle --------------------------- ``` -$ mad release emqttd -$ ./emqttd rep +$ make tar ``` User/Device Registration @@ -160,32 +106,9 @@ ok > emqttd_ctl:run(["help"]). ``` -MQTT Erlang Client ------------------- - -``` -$ mad com -==> "/Users/maxim/depot/voxoz/emqttc/examples/gen_server" -Compiling /src/gen_server_example.erl -Writing /ebin/gen_server_example.app -OK -bash-3.2$ ./run -Erlang/OTP 19 [erts-8.2] [source] [64-bit] [smp:4:4] - [async-threads:10] [hipe] [kernel-poll:false] [dtrace] - -Eshell V8.2 (abort with ^G) -1> [info] [Client <0.58.0>]: connecting to 127.0.0.1:1883 -[info] [Client <0.58.0>] connected with 127.0.0.1:1883 -[info] [Client <0.58.0>] RECV: CONNACK_ACCEPT -Client <0.58.0> is connected -[warning] [simpleClient@127.0.0.1:64618] resubscribe [{<<"TopicA">>,1}] -Message from TopicA: <<"hello...1">> -Message from TopicB: <<"hello...1">> -``` Credits ------- * Maxim Sokhatsky - -OM A HUM +* Ulf Wiger diff --git a/apps/rebar.config b/apps/rebar.config deleted file mode 100644 index c337a9c61111b599156aff4ca5e7b7e0d4ea6787..0000000000000000000000000000000000000000 --- a/apps/rebar.config +++ /dev/null @@ -1,2 +0,0 @@ -{sub_dirs, [ "roster", "service" ]}. -{deps_dir, ["../deps"]}. diff --git a/apps/roster/include/roster.hrl b/apps/roster/include/roster.hrl index bbd71b7e2a5b29864a6cbed8192ff00ec5890896..f8375165b1da18f1bd344af80ec9bd175f5ddcdf 100644 --- a/apps/roster/include/roster.hrl +++ b/apps/roster/include/roster.hrl @@ -140,7 +140,7 @@ -record('Link', {id = [] :: [] | binary(), %% link value name = [] :: [] | binary(), %% unused atm room_id = [] :: [] | binary(), %% parent Room id - created = [] :: [] | integer(), + created = [] :: [] | integer(), type = [] :: [] | group | channel, status = [] :: [] | gen | check | add %% old unused fields | get | join | update | delete}). %% new fields @@ -158,7 +158,7 @@ tos_update = 0 :: [] | integer(), unread = 0 :: [] | integer(), mentions = [] :: list(integer()), - readers = [] :: list(integer()), + readers = [] :: list(integer()) | none | {last_read, integer()}, %% last_read :: MsgId last_msg = [] :: [] | integer() | #'Message'{}, update = 0 :: [] | integer(), created = 0 :: [] | integer(), @@ -230,7 +230,7 @@ status = [] :: [] | get | create | del | remove | nick | add | update | list | patch | last_msg }). --record('Profile', +-record('Profile', { phone = [] :: [] | binary(), services = [] :: [] | list(#'Service'{}), @@ -239,7 +239,7 @@ update = 0 :: integer(), balance = 0 :: integer(), presence = [] :: [] | offline | online | binary(), - status = [] :: [] | remove | get | patch | update| delete | create + status = [] :: [] | remove | get | patch | update| delete | create }). -record('Presence', {uid = <<>> :: binary(), @@ -301,7 +301,7 @@ -record('MessageErr', {feed_id = [] :: #muc{} | #p2p{}, msg_id = [] :: [] | binary(), error = [] :: [] | #error{}}). - + -record(io, {code = [] :: [] | #ok{} | #error{} | #ok2{} | #error2{} | transcribe | #'MessageErr'{}, data = <<>> :: [] | <<>> | #'Roster'{} | { atom(), binary() | integer() }}). diff --git a/apps/roster/include/roster_test.hrl b/apps/roster/include/roster_test.hrl index 8cc0a85752fde4e64135c98c649904d1675722f1..cdcfe4b94c527479a3ff79ff87a13561bc85d0f1 100644 --- a/apps/roster/include/roster_test.hrl +++ b/apps/roster/include/roster_test.hrl @@ -2,6 +2,7 @@ -define(ROSTER_TEST_HRL, true). -define(LOC, "127.0.0.1"). +%%-define(REMOTE_NODE, true). -define(HOST, ?LOC). -define(DRP_TMOUT, case ?HOST of ?LOC -> 100;_ -> 1000 end). -define(TIMEOUT, 7000). diff --git a/apps/roster/include/static/rest_var.hrl b/apps/roster/include/static/rest_var.hrl deleted file mode 100644 index 2c13ed749a0e10fabd6c4cb04108a06f30502f4a..0000000000000000000000000000000000000000 --- a/apps/roster/include/static/rest_var.hrl +++ /dev/null @@ -1,45 +0,0 @@ -%% ------------------------------------------------------------------ -%% Static Variables for REST modules -%% ------------------------------------------------------------------ - -%% Endpoints --define(SESSIONS_ENDPOINT, "/sessions"). - --define(WHITELIST_ENDPOINT, "/whitelist"). --define(ADMIN_WHITELIST_ENDPOINT, "/admin_whitelist"). - -%% FN is for Fake Numbers --define(ADMIN_FN_ENDPOINT, "/fake_numbers"). --define(FN_ENDPOINT, "/fn"). - --define(MSG_PUSH_ENDPOINT, "/push/message"). --define(ROOM_ENDPOINT, "/room"). --define(PUBLISH_ENDPOINT, "/publish"). --define(USERS_ENDPOINT, "/users"). --define(METRICS_ENDPOINT, "/metrics"). - --define(RCI_ROOM_TYPE_ENDPOINT, "/cri/rooms/type"). --define(RCI_ROOM_ENDPOINT, "/cri/rooms"). --define(RCI_ROOM_MEMBERS_ENDPOINT, "/cri/rooms/members"). --define(RCI_BUBBLE_ENDPOINT, "/cri/bubbles"). - -%% Request Query and Body Param Names --define(PHONE_ID_HEADER, "PhoneId"). - --define(ROOM_ID_PARAM, "room_id"). --define(PHONE_IDS_PARAM, "phone_ids"). --define(PHONE_ID_PARAM, "phone_id"). --define(JOIN_FLAG_PARAM, "join"). - -%% sessions module --define(PHONE_PARAM, "phone"). - - - -%% HTTP Status Codes --define(HTTP_CODE_200, 200). --define(HTTP_CODE_400, 400). --define(HTTP_CODE_401, 401). --define(HTTP_CODE_403, 403). --define(HTTP_CODE_404, 404). --define(HTTP_CODE_405, 405). \ No newline at end of file diff --git a/apps/roster/priv/www/assets/js/phone_numbers.js b/apps/roster/priv/www/assets/js/phone_numbers.js index fbfd969dc98069093ccca8abfe61b0c65184ce50..f6f496806a3b2ef0d69b048c5c855f68048e81a4 100644 --- a/apps/roster/priv/www/assets/js/phone_numbers.js +++ b/apps/roster/priv/www/assets/js/phone_numbers.js @@ -17,11 +17,8 @@ $(document).ready(function() { url: GetCurrentUrl(), type: 'GET', success: function(data) { - var parsed_data = JSON.parse(data); - var status = parsed_data["Status"]; - var payload = parsed_data["Data"]; - if (status == "Success" ) { - FillWhitelist(payload, order); + if (data.Status == "Success" ) { + FillWhitelist(data.Data, order); } } }); @@ -31,11 +28,10 @@ $(document).ready(function() { $.ajax({ url: GetCurrentUrl(), type: 'PUT', + contentType: 'application/json', data: '{"phone":['+ data + ']}', success: function(data) { - var parsed_data = JSON.parse(data); - var status = parsed_data["Status"]; - if (status == "Success" ) { + if (data.Status == "Success" ) { document.getElementById('close-modal-window').click(); GetWhitelistFromServer("down"); } @@ -57,9 +53,7 @@ $(document).ready(function() { url: GetCurrentUrl() + '?' + $.param({'phone': data}, true), type: 'DELETE', success: function(data) { - var parsed_data = JSON.parse(data); - var status = parsed_data["Status"]; - if (status == "Success" ) { + if (data.Status == "Success" ) { GetWhitelistFromServer("down"); } }, @@ -200,4 +194,4 @@ $(document).ready(function() { element.dataset.order = new_order; GetWhitelistFromServer(new_order) }); -}); \ No newline at end of file +}); diff --git a/apps/roster/rebar.config b/apps/roster/rebar.config deleted file mode 100644 index a3a320496122b26a277284d0f2e3cd5d95a77267..0000000000000000000000000000000000000000 --- a/apps/roster/rebar.config +++ /dev/null @@ -1,4 +0,0 @@ -{deps_dir, ["../../deps"]}. -{lib_dirs, ["../../apps"]}. -{deps, []}. -{erl_opts, [debug_info]}. \ No newline at end of file diff --git a/apps/roster/src/api/amazon_api.erl b/apps/roster/src/api/amazon_api.erl index 2768e3941f2a1a6624e13f0f197521b213d08ba0..a8c47277377fe2013c4b74372c530d1d6484b77f 100644 --- a/apps/roster/src/api/amazon_api.erl +++ b/apps/roster/src/api/amazon_api.erl @@ -1,4 +1,5 @@ -module(amazon_api). +-include_lib("kernel/include/logger.hrl"). -export([push_to_s3/4, test_upload/0, gen_temp_creds/0, test_gen_temp_creds/0, test_parse_sts_response/0]). -define(AWS_ACCESS_KEY_ID, proplists:get_value(access_key_id, application:get_env(roster, amazon_api, []))). @@ -28,6 +29,10 @@ %% Helpers %% **************************************************************************** +content_type(Filename) -> + {Class, Type, _} = cow_mimetypes:all(Filename), + iolist_to_binary([Class, "/", Type]). + %% ---------------------------------------------------------------------------- %% AWS S3 helpers %% ---------------------------------------------------------------------------- @@ -67,17 +72,17 @@ gen_hash(Data) -> parse_sts_response(Data) -> try - {_, DecodedIn} = mochijson:decode(Data), - {_, GetSessionTokenResponse} = proplists:get_value("GetSessionTokenResponse", DecodedIn), - {_, GetSessionTokenResult} = proplists:get_value("GetSessionTokenResult", GetSessionTokenResponse), - {_, Credentials} = proplists:get_value("Credentials", GetSessionTokenResult), - AccessKeyId = proplists:get_value("AccessKeyId", Credentials), - SecretAccessKey = proplists:get_value("SecretAccessKey", Credentials), - SessionToken = proplists:get_value("SessionToken", Credentials), - Expiration = erlang:round(proplists:get_value("Expiration", Credentials)), + DecodedIn = jsx:decode(iolist_to_binary(Data)), + GetSessionTokenResponse = proplists:get_value(<<"GetSessionTokenResponse">>, DecodedIn), + GetSessionTokenResult = proplists:get_value(<<"GetSessionTokenResult">>, GetSessionTokenResponse), + Credentials = proplists:get_value(<<"Credentials">>, GetSessionTokenResult), + AccessKeyId = proplists:get_value(<<"AccessKeyId">>, Credentials), + SecretAccessKey = proplists:get_value(<<"SecretAccessKey">>, Credentials), + SessionToken = proplists:get_value(<<"SessionToken">>, Credentials), + Expiration = erlang:round(proplists:get_value(<<"Expiration">>, Credentials)), {ok, {AccessKeyId, SecretAccessKey, SessionToken, Expiration}} catch - E:R -> roster:info(?MODULE, "~p:Parsing AWS STS Response Failed!~p:~p", [Data, E, R]), + E:R -> ?LOG_INFO("~p:Parsing AWS STS Response Failed!~p:~p(~p)", [Data, E, R]), {error, R} end. @@ -92,8 +97,7 @@ push_to_s3(BucketName, Key, Data, Headers) when is_binary(BucketName) -> push_to_s3(BucketName, Key, Data, Headers) when is_binary(Key) -> push_to_s3(BucketName, binary_to_list(Key), Data, Headers); push_to_s3(BucketName, Key, Data, Headers) when Headers == [] -> - ContentType = mochiweb_mime:from_extension(filename:extension(Key)), - RequestHeaders = [{"content-type", ContentType}], + RequestHeaders = [{"content-type", content_type(Key)}], push_to_s3(BucketName, Key, Data, RequestHeaders); push_to_s3(BucketName, Key, Data, Headers) -> UniqueKey = make_unique_key(Key), @@ -102,7 +106,7 @@ push_to_s3(BucketName, Key, Data, Headers) -> mini_s3:put_object(BucketName, UniqueKey, Data, [{acl, public_read}], Headers), get_s3_link(BucketName, UniqueKey) catch E:R -> - roster:info(?MODULE, "Pushing to Amazon S3 failed! ~p:~p", [E,R]), + ?LOG_INFO("Pushing to Amazon S3 failed! ~p:~p", [E,R]), {error, R} end. %% **************************************************************************** @@ -137,7 +141,7 @@ gen_temp_creds() -> ok -> parse_sts_response(RequestResponse); _ -> {error, RequestResponse} end, - roster:info(?MODULE, "STSGetSessionTokenResponse:~p", [Response]), + ?LOG_INFO("STSGetSessionTokenResponse:~p", [Response]), Response. %% **************************************************************************** @@ -150,6 +154,7 @@ gen_temp_creds() -> -define(AMAZON_TEST_APPLICATION_NAME, roster). +%% TODO use sys.config for location and possibly add at same place as google api read_local_file(Filename) -> case code:priv_dir(?AMAZON_TEST_APPLICATION_NAME) of {error, bad_name} -> @@ -157,17 +162,16 @@ read_local_file(Filename) -> PrivDir -> ok end, - {Status, Binary} = file:read_file(filename:join([PrivDir, Filename])), - case Status of - error -> - roster:info(?MODULE, "Cannot read local file!~n~p:~p", [Status, Binary]); - ok -> + Path = filename:join(PrivDir, Filename), + case file:read_file(Path) of + {error, Reason} -> + ?LOG_INFO("Cannot read local file ~p: ~p", [Path, Reason]); + {ok, Binary} -> prepare_file_to_uploading(Filename, Binary) end. prepare_file_to_uploading(Filename, Data) -> - ContentType = mochiweb_mime:from_extension(filename:extension(Filename)), - Headers = [{"content-type", ContentType}], + Headers = [{"content-type", content_type(Filename)}], {A, B, C} = os:timestamp(), Timestamp = lists:concat([A, B, C]), Key = lists:concat(["TestFile", Timestamp, filename:extension(Filename)]), @@ -188,4 +192,4 @@ test_gen_temp_creds() -> test_parse_sts_response() -> Data = "{\"GetSessionTokenResponse\":{\"GetSessionTokenResult\":{\"Credentials\":{\"AccessKeyId\":\"ASIAJ6H77ARLVWEMXGAQ\",\"Expiration\":1.507853429E9,\"SecretAccessKey\":\"Xb0wOH4PyYe5YaJBu7TqjSAXRq2vaInPR1JbSiRJ\",\"SessionToken\":\"FQoDYXdzEO3//////////wEaDIbWWOcDonXumUQViSKsAeRh+g/QQGrYWpxxBwkX1diNpalwK29hlUlh0a//zmEP5ZuuWbQPMiW0JxbPltnj0G4T1S9Nt7qdOUUNsCPNc/1UKp6SrO26kdC1dvbfyWcQbpABROUtXn0nN/Du+94aM8Fe4uUgXcNJdu0NILz4U5WQRT5h/Z5k388ZjHQxHTqDxw/waIEy5PVGQyQvjv0HvN+kdDgGdVdfMIhEeBS9ucNMxtYagwIc8M9guQMotbf9zgU=\"}},\"ResponseMetadata\":{\"RequestId\":\"56cb6126-af46-11e7-8109-ed1456c23af6\"}}}", {Status, _} = parse_sts_response(Data), - ok == Status. \ No newline at end of file + ok == Status. diff --git a/apps/roster/src/api/email_api.erl b/apps/roster/src/api/email_api.erl index 08c9155cf6abcf32a35c01396b75cef031f4afc2..2830f4485b97721126f1b40af4bb721d5334f084 100644 --- a/apps/roster/src/api/email_api.erl +++ b/apps/roster/src/api/email_api.erl @@ -1,6 +1,7 @@ -module(email_api). -compile(export_all). +-include_lib("kernel/include/logger.hrl"). -define(SMTP_HOST, proplists:get_value(host, application:get_env(roster,email_api, []))). -define(EMAIL_LOGIN, proplists:get_value(login, application:get_env(roster,email_api, []))). @@ -12,7 +13,7 @@ %% ------------------------------------------------------------------ send_email(ToEmail,Subject, Body) -> - roster:info(?MODULE, "~p:Email ~p:~p", [ToEmail, Subject, Body]), + ?LOG_INFO("~p:Email ~p:~p", [ToEmail, Subject, Body]), %% create payload @@ -23,11 +24,11 @@ send_email(ToEmail,Subject, Body) -> R = gen_smtp_client:send({ToEmail, [ToEmail], EncodedBody}, [{relay, ?SMTP_HOST}, {username, ?EMAIL_LOGIN}, {password, ?EMAIL_PASSWORD}]), - roster:info(?MODULE, "Gen SMTP Response:~p", [R]), + ?LOG_INFO("Gen SMTP Response:~p", [R]), case R of {ok, _} -> {ok, email_in_progress}; {error, _, {_, _, Reason}} -> - roster:info(?MODULE, "Reason:~p", [Reason]), + ?LOG_INFO("Reason:~p", [Reason]), {error, Reason} end. @@ -40,4 +41,4 @@ send_email(ToEmail,Subject, Body) -> test_email_sending() -> Body = <<"Hello,

This is test mail! Playing with HTML letters!">>, Subject = iolist_to_binary(["Test ", vox_api:generate_random_data(4)]), - send_email(?TEST_EMAIL, Subject, Body). \ No newline at end of file + send_email(?TEST_EMAIL, Subject, Body). diff --git a/apps/roster/src/api/google_api.erl b/apps/roster/src/api/google_api.erl index a0f5d4591004d3ee5efaed5f651502b239a00523..58e8e5bef8b16c3f72873fcf8f595dab1c4d45fb 100644 --- a/apps/roster/src/api/google_api.erl +++ b/apps/roster/src/api/google_api.erl @@ -1,6 +1,7 @@ -module(google_api). -compile(export_all). +-include_lib("kernel/include/logger.hrl"). -include_lib("enenra/include/enenra.hrl"). -define(GOOGLE_API_KEY, proplists:get_value(key, application:get_env(roster, google_api, []))). @@ -27,50 +28,52 @@ %% ------------------------------------------------------------------ detect_language(Text) -> - roster:info(?MODULE, "DetectLanguage: ~p", [Text]), + ?LOG_INFO("DetectLanguage: ~p", [Text]), %% TODO encode url symbols. Not all text, just whitespaces, dots, etc. %% NOTE! Dont use http_uri:encode. This lib encodes all text, fuuuu QueryParams = iolist_to_binary(["key=", ?GOOGLE_API_KEY]), PostVariables = jsx:encode([{<<"q">>, Text}]), - roster:info(?MODULE, "PostVariables: ~p", [PostVariables]), + ?LOG_INFO("PostVariables: ~p", [PostVariables]), Request = binary_to_list(iolist_to_binary([?DETECT_LANGUAGE_URL, "?", QueryParams])), - roster:info(?MODULE, "Request: ~p", [Request]), + ?LOG_INFO("Request: ~p", [Request]), {RequestStatus, RequestResult} = roster_rest:send_request(post, {Request, [], ?CONTENT_TYPE, PostVariables}, [], []), - roster:info(?MODULE, "RequestStatus: ~p", [RequestStatus]), - roster:info(?MODULE, "RequestResult: ~p", [RequestResult]), + ?LOG_INFO("RequestStatus: ~p", [RequestStatus]), + ?LOG_INFO("RequestResult: ~p", [RequestResult]), ok. translate(Text, Target) -> - roster:info(?MODULE, "Debug.Target:~p.Text: ~p", [Target, Text]), + ?LOG_INFO("Debug.Target:~p.Text: ~p", [Target, Text]), QueryParams = iolist_to_binary(["key=", ?GOOGLE_API_KEY]), PostVariables = jsx:encode([{<<"q">>, Text}, {<<"target">>, Target}]), - roster:info(?MODULE, "PostVariables: ~p", [PostVariables]), + ?LOG_INFO("PostVariables: ~p", [PostVariables]), Request = binary_to_list(iolist_to_binary([?TRANSLATION_URL, "?", QueryParams])), {RequestStatus, RequestResult} = roster_rest:send_request(post, {Request, [], ?CONTENT_TYPE, PostVariables}, [], []), - roster:info(?MODULE, "RequestStatus: ~p", [RequestStatus]), - roster:info(?MODULE, "RequestResult: ~p", [RequestResult]), + ?LOG_INFO("RequestStatus: ~p", [RequestStatus]), + ?LOG_INFO("RequestResult: ~p", [RequestResult]), ok. %% ------------------------------------------------------------------ %% Transcribe API %% ------------------------------------------------------------------ start() -> - filelib:ensure_dir("./priv/tmp/"), - gen_server:call(ibrowse, {set_config_value, download_dir, "./priv/tmp/"}), - PathFile = filename:absname(proplists:get_value(app_credentials, application:get_env(roster, google_api, indefined))), - case filelib:is_file(PathFile) of - true -> - os:putenv("GOOGLE_APPLICATION_CREDENTIALS", PathFile), - case enenra:load_credentials(os:getenv("GOOGLE_APPLICATION_CREDENTIALS")) of - {ok, Creds} -> application:set_env(roster, google_creds, Creds); - Err-> roster:error(?MODULE, "cannot load googlr creds: ~p", [Err]) - end; - _ -> roster:error(?MODULE, "google credential file ~p not found", PathFile) - end, - case ?GOOGLE_API_KEY of - [] -> get_access_token(); - <<"AIza", _/binary>> -> ok - end. + filelib:ensure_dir("./priv/tmp/"), + gen_server:call(ibrowse, {set_config_value, download_dir, "./priv/tmp/"}), + PathFile = filename:absname(proplists:get_value(app_credentials, + application:get_env(roster, google_api, []))), + os:putenv("GOOGLE_APPLICATION_CREDENTIALS", PathFile), + case filelib:is_file(PathFile) of + true -> + case enenra:load_credentials(PathFile) of + {ok, Creds} -> application:set_env(roster, google_creds, Creds); + Err-> ?LOG_ERROR("cannot load google credentials: ~p", [Err]) + end; + _ -> + ?LOG_ERROR("google credential file ~p not found", PathFile) + end, + case ?GOOGLE_API_KEY of + [] -> get_access_token(); + <<"AIza", _/binary>> -> ok + end. del_gs_object(<<"gs://", Path/binary>>) -> Bucket = ?GS_BUCKET, @@ -78,8 +81,8 @@ del_gs_object(<<"gs://", Path/binary>>) -> [Bucket|PathObject] -> Object = http_uri:encode(roster:binary_join(PathObject, <<"/">>)), case del_gs_object(Bucket, Object) of - ok -> roster:info(?MODULE, "object ~p is deleted successfully", [Object]), ok; - Err -> roster:error(?MODULE, "cannot delete object ~p", [Err]), Err + ok -> ?LOG_INFO("object ~p is deleted successfully", [Object]), ok; + Err -> ?LOG_ERROR("cannot delete object ~p", [Err]), Err end; [_|_] -> ok end. @@ -87,7 +90,7 @@ del_gs_object(Bucket, Object) -> case application:get_env(roster, google_creds) of {ok, Creds} -> enenra:delete_object(Bucket, Object, Creds); - undefined -> roster:error(?MODULE, "creds not found to delete GS object", []), + undefined -> ?LOG_ERROR("creds not found to delete GS object", []), {error, creds_not_found} end. uri_to_gs(<<"https://www.googleapis.com/storage/", _/binary>> = Uri) -> @@ -111,7 +114,7 @@ token_expiration(Token) -> #{<<"exp">> := ExpTime} = jsx:decode(list_to_binary(Body), [return_maps]), {Token, binary_to_integer(ExpTime)}; {error, E} -> - roster:error(?MODULE, "invalid access token: ~p", [E]), + ?LOG_ERROR("invalid access token: ~p", [E]), {error, invalid_access_token} end. @@ -125,11 +128,11 @@ get_access_token() -> "ya29"++_ = Token-> application:set_env(roster, access_token, token_expiration(Token)), Token; ErrInfo -> - roster:error(?MODULE, "invalid acess token: ~p", [ErrInfo]), + ?LOG_ERROR("invalid acess token: ~p", [ErrInfo]), application:set_env(roster, access_token, {error, invalid_access_token}), {error, invalid_access_token} end; - {Token, ExpTime} -> Token + {Token,_ExpTime} -> Token end; <<"AIza", _/binary>> = Key -> Key end. @@ -148,7 +151,7 @@ transcribe(Type, Data, Lang, Encoding, ReturnFun) when Type == short; Type == lo {long, <<"gs://", _/binary>> = GS} -> {?TRANSCRIBE_LONG_URL, #{uri => GS}}; {short, <<"gs://", _/binary>> = GS} -> {?TRANSCRIBE_URL, #{uri => GS}}; {short, _} -> {?TRANSCRIBE_URL, #{content => Data}}; - {long, _} -> roster:error(?MODULE, "invalid url for transcribe of long audio: ~p", [Data]), + {long, _} -> ?LOG_ERROR("invalid url for transcribe of long audio: ~p", [Data]), {error, invalid_data} end, case D of {error, _} -> ReturnFun(D); @@ -162,7 +165,7 @@ transcribe(Type, Data, Lang, Encoding, ReturnFun) when Type == short; Type == lo {ok, Result} -> case jsx:decode(list_to_binary(Result), [return_maps]) of #{<<"results">> := Alternatives} when Type == short -> -%% roster:info(?MODULE, "alternative transcribes: ~p", [Alternatives]), +%% ?LOG_INFO("alternative transcribes: ~p", [Alternatives]), ReturnFun( case merge_transcribe(Alternatives) of <<>> -> {error, invalid_transcribe}; @@ -170,7 +173,7 @@ transcribe(Type, Data, Lang, Encoding, ReturnFun) when Type == short; Type == lo #{<<"name">> := OperationName} -> LongRequest = {access_url(?TRANSCRIBE_OPERATIONS_URL ++ OperationName, AccessToken), AuthHeaders}, send_operation(LongRequest, OperationName, ?LONG_TRANSCRIBE_COUNTER, ?LONG_TRANSCRIBE_TIMEOUT, ReturnFun); - #{} -> roster:error(?MODULE, "no text for transcribe in audio file", []), + #{} -> ?LOG_ERROR("no text for transcribe in audio file", []), ReturnFun({error, invalid_transcribe}) end; {error, _} = Err -> ReturnFun(Err) @@ -186,7 +189,7 @@ merge_transcribe(Alternatives) -> send_operation(_LongRequest, _OperationName, 0, _Timeout, Fun) -> Fun({error, timeout}); send_operation(LongRequest, OperationName, Counter, Timeout, Fun) -> -%% roster:info(?MODULE, "transcribe counter = ~p", [Counter]), +%% ?LOG_INFO("transcribe counter = ~p", [Counter]), case roster_rest:send_request(get, LongRequest, [], []) of {ok, R} -> case jsx:decode(list_to_binary(R), [return_maps]) of @@ -198,7 +201,7 @@ send_operation(LongRequest, OperationName, Counter, Timeout, Fun) -> <<>> -> {error, invalid_transcribe}; Text -> Text end); #{<<"name">> := OperationName} -> timer:apply_after(Timeout, ?MODULE, send_operation, [LongRequest, OperationName, Counter - 1, Timeout, Fun]); - #{} = R -> roster:error("invalid long operation ~p", [R]), + #{} = R -> ?LOG_ERROR("invalid long operation ~p", [R]), Fun({error, invalid_transcribe}) end; {error, _} = Err -> Fun(Err) @@ -209,7 +212,7 @@ download(Uri, Timeout) -> case ibrowse:send_req(Uri, [], get, [], [{save_response_to_file, true}], Timeout) of {ok, "200", _, File} -> File; {ok, _, _, ErrString} = Res-> - roster:error(?MODULE, "~p", [Res]), + ?LOG_ERROR("~p", [Res]), {error, ErrString}; Error -> Error end. @@ -225,7 +228,7 @@ convert_ffmpeg(FileIn) -> Result = os:cmd(io_lib:format(?CONVERT_CMD, [FileIn, FileIn])), case filelib:is_file(FileOut = FileIn++".wav") of true -> {file, FileOut, FileIn}; - _ -> roster:error("ffmpeg error:~n~p", [Result]), + _ -> ?LOG_ERROR("ffmpeg error:~n~p", [Result]), {error, file_not_created} end. @@ -243,15 +246,16 @@ gs_upload(FileIn, Bucket, ContentType) -> case enenra:upload_file(FileOut, #object{name = Name, bucket = Bucket, contentType = ContentType, md5Hash = Md5, size = Size}, Creds) of {ok, #object{}} -> - roster:info(?MODULE, "object ~p is uploaded successfully", [GsUri = iolist_to_binary(["gs://",Bucket,"/",Name])]), + GsUri = iolist_to_binary(["gs://",Bucket,"/",Name]), + ?LOG_INFO("object ~p is uploaded successfully", [GsUri]), [file:delete(filename:absname(File)) || File<-[FileIn2, FileOut]], {gs, GsUri}; Err -> - roster:error(?MODULE, "upload file ~p to Google Storage is failed: ~p", [FileOut, Err]), + ?LOG_ERROR("upload file ~p to Google Storage is failed: ~p", [FileOut, Err]), Err end; undefined -> - roster:error(?MODULE, "google creds not found", []), + ?LOG_ERROR("google creds not found", []), {error, creds_not_found} end; Err -> Err @@ -283,16 +287,16 @@ test_transcribe() -> {ok, Binary} = file:read_file("apps/roster/priv/transcribe_test.flac"), <<_:8, _/binary>> = google_api:transcribe(short, base64:encode(Binary), <<"en-US">>). test_long_transcribe() -> - roster:info(?MODULE, "wait about 30 sec", []), + ?LOG_INFO("wait about 30 sec", []), spawn( fun() -> Pid = self(), google_api:transcribe(long, <<"gs://gcs-test-data/vr.flac">>, <<"en-US">>, <<"ENCODING_UNSPECIFIED">>, fun(Text) -> Pid ! Text end), receive - <<_:8, _/binary>> = Text -> roster:info(?MODULE, "~p", [Text]), Text + <<_:8, _/binary>> = Text -> ?LOG_INFO("~p", [Text]), Text after - 50000 -> roster:info(?MODULE, "subscribe timeout", []) + 50000 -> ?LOG_INFO("subscribe timeout", []) end end). @@ -306,15 +310,15 @@ test_convert_transcribe_long() -> test_convert_transcribe_long("https://gcs-test-data.storage.googleapis.com/vr.flac"). test_convert_transcribe_long(Url) -> {gs, GsUri} = gs_upload(Url), - roster:info(?MODULE, "\tbegin long transcribe~n\t\twait about 30 sec", []), + ?LOG_INFO("\tbegin long transcribe~n\t\twait about 30 sec", []), spawn( fun() -> Pid = self(), google_api:transcribe(long, GsUri, <<"en-US">>, <<"ENCODING_UNSPECIFIED">>, fun(Text) -> del_gs_object(GsUri), Pid ! Text end), receive - <<_:8, _/binary>> = Text -> roster:info(?MODULE, "~p", [Text]), Text + <<_:8, _/binary>> = Text -> ?LOG_INFO("~p", [Text]), Text after - 50000 -> roster:info(?MODULE, "transcribe timeout", []) + 50000 -> ?LOG_INFO("transcribe timeout", []) end end). diff --git a/apps/roster/src/api/prometheus_api.erl b/apps/roster/src/api/prometheus_api.erl index 08e3d07497bb8f4d91fc830e293cadb538e023a6..55b5693f434c67d0cb0f648656ccc6c79c0fa8d0 100644 --- a/apps/roster/src/api/prometheus_api.erl +++ b/apps/roster/src/api/prometheus_api.erl @@ -2,6 +2,7 @@ %% Prometheus metrics declaration module -module(prometheus_api). +-include_lib("kernel/include/logger.hrl"). -include_lib("roster/include/static/prometheus_text.hrl"). -include_lib("roster/include/static/prometheus_var.hrl"). @@ -20,7 +21,7 @@ init() -> ], [begin prometheus_gauge:declare([{name, Name}, {help, Desc}, {labels, Label}]), - roster:info(?MODULE, "InitMetric:~p", [Name]) + ?LOG_INFO("InitMetric:~p", [Name]) end || {Name, Desc, Label} <- GaugeMetrics], prometheus_histogram:declare([{name, ?ROSTER_REQUEST_LATENCY}, {labels, [request]}, diff --git a/apps/roster/src/api/push/android.erl b/apps/roster/src/api/push/android.erl index 3db053955524ec3e7a7bb7d901634fbd715ddad9..db15d4c91e67bce9b68001655ec2723a5f03353e 100644 --- a/apps/roster/src/api/push/android.erl +++ b/apps/roster/src/api/push/android.erl @@ -1,4 +1,5 @@ -module(android). +-include_lib("kernel/include/logger.hrl"). -include("roster.hrl"). -export([description/0, notify/3, test_push_notification/0]). @@ -27,7 +28,7 @@ notify(MessageTitle, MessageBody, DeviceId) when is_binary(DeviceId) -> notify(_, MessageBody, DeviceId) -> %% create payload Payload = binary_to_list(iolist_to_binary(["registration_id=", DeviceId, "&data=", MessageBody, "&priority=high"])), - roster:info(?MODULE, "Payload ~p~n~n", [Payload]), + ?LOG_INFO("Payload ~p~n~n", [Payload]), Request = {?FCM_SEND_ENDPOINT, fcm_headers(), ?FCM_CONTENT_TYPE, Payload}, {_, _} = roster_rest:send_request(post, Request, [], []), ok. diff --git a/apps/roster/src/api/push/ios.erl b/apps/roster/src/api/push/ios.erl index 6d89196ed023fe583973976235986e8e9f4079fb..8a8f011671cf8093a79aa5073810319a94d0d740 100644 --- a/apps/roster/src/api/push/ios.erl +++ b/apps/roster/src/api/push/ios.erl @@ -1,4 +1,5 @@ -module(ios). +-include_lib("kernel/include/logger.hrl"). -include("roster.hrl"). -include_lib("roster/include/static/push_notification_var.hrl"). @@ -35,7 +36,7 @@ notify(A, C, T, DeviceId, SessionSettings) -> %% create ios payload string PayloadString = binary_to_list(iolist_to_binary(["{\"aps\": {\"nynja\": ", Aps, "}}"])), -% roster:info(roster, "PayloadString ~p~n~n", [PayloadString]), +% ?LOG_INFO("PayloadString ~p~n~n", [PayloadString]), %% prepare push data Payload = list_to_binary(PayloadString), @@ -43,7 +44,7 @@ notify(A, C, T, DeviceId, SessionSettings) -> FormattedDeviceId = list_to_integer(DeviceId, 16), Packet = <<0:8, 32:16/big, FormattedDeviceId:256/big, PayloadLength:16/big, Payload/binary>>, {_, {CertFile, KeyFile}} = get_bandle(SessionSettings), - roster:info(?MODULE, "CertFile: ~p", [CertFile]), + ?LOG_INFO("CertFile: ~p", [CertFile]), Options = [{certfile, path_to_pem_file(CertFile)}, {keyfile, path_to_pem_file(KeyFile)}, {mode, binary}], [send_push(Addr, Packet, Options, 1) || {_, Addr} <- get_gateway(SessionSettings)], ok. @@ -53,7 +54,7 @@ notify(A, C, T, DeviceId, SessionSettings) -> %% ------------------------------------------------------------------ send_push(Addr, Payload, Options, Attempt) -> - roster:info(?MODULE, "Addr: ~p, Attempt:~p", [Addr, Attempt]), + ?LOG_INFO("Addr: ~p, Attempt:~p", [Addr, Attempt]), %% NOTE set Duration = Attempt * 100 for tests Duration = Attempt * 500, {Status, Socket} = ssl:connect(Addr, ?APNS_PORT, Options, Duration), @@ -61,14 +62,14 @@ send_push(Addr, Payload, Options, Attempt) -> ok -> ssl:send(Socket, Payload), ssl:close(Socket), - roster:info(?MODULE, "Push sent", []); + ?LOG_INFO("Push sent", []); error -> if Attempt > 10 -> - roster:info(?MODULE, "Final error", []); + ?LOG_INFO("Final error", []); true -> timer:sleep(Duration), - roster:info(?MODULE, "Error with socket opening. Reason:~p", [Socket]), + ?LOG_INFO("Error with socket opening. Reason:~p", [Socket]), send_push(Addr, Payload, Options, Attempt + 1) end end. @@ -113,4 +114,4 @@ test_push_notification() -> #'Feature'{id = <<"ID_Bandle">>, key = <<"IOS_BANDLE">>,value = <<"com.nynja.mobile.communicator">>, group = <<"AUTH_DATA">>}], Msg = lists:concat(["Test it! ", vox_api:generate_random_data(4)]), Custom = <<"g2gSZAAHTWVzc2FnZWEQZAAFY2hhaW5oA2QAA3AycG0AAAAOMzgwNjM4MDk1MTU4XzdtAAAADjM4MDk5NDM4Mjc5OF84ampqam0AAAAOMzgwOTk0MzgyNzk4XzhtAAAADjM4MDYzODA5NTE1OF83am4GAD5BRlNeAWpqbAAAAAFoBmQABERlc2NqbQAAAARIaGhoYQBqampqamQABHNlbnQ=">>, - notify(Msg, Custom, <<"message">>, ?APNS_TEST_DEVICE_ID, SessionSettings). \ No newline at end of file + notify(Msg, Custom, <<"message">>, ?APNS_TEST_DEVICE_ID, SessionSettings). diff --git a/apps/roster/src/api/regexp_api.erl b/apps/roster/src/api/regexp_api.erl index d3f88d0a270835e0ccc30351407bcd1e2888e259..357d8ec69ace7eef436374966ec8f35fce1969af 100644 --- a/apps/roster/src/api/regexp_api.erl +++ b/apps/roster/src/api/regexp_api.erl @@ -1,4 +1,5 @@ -module(regexp_api). +-include_lib("kernel/include/logger.hrl"). -compile(export_all). %% NOTE! Max, this is just-for-fun module. I can delete it later (if you wish) @@ -11,18 +12,18 @@ validate_string(String) when String == [] orelse String == <<>> -> {ok, []}; validate_string(String) -> validate_string(String, <<"^[a-zA-Z0-9_].{1,32}$">>). validate_string(String, RegExp) -> - roster:info(?MODULE, "StringToValidate:~p", [String]), - roster:info(?MODULE, "RegExp:~p", [RegExp]), + ?LOG_INFO("StringToValidate:~p", [String]), + ?LOG_INFO("RegExp:~p", [RegExp]), Status = try case re:run(String, RegExp) of {match, _} -> ok; nomatch -> error end catch E:R -> - roster:info(?MODULE, "RegExpError:~p:~p", [E, R]), + ?LOG_INFO("RegExpError:~p:~p", [E, R]), error end, - roster:info(?MODULE, "ValidationStatus:~p", [Status]), + ?LOG_INFO("ValidationStatus:~p", [Status]), {Status, String}. %% ------------------------------------------------------------------ @@ -83,4 +84,4 @@ test_neg7() -> %% Nick with just space Str = <<" ">>, {Status, _} = validate_string(Str), - Status == error. \ No newline at end of file + Status == error. diff --git a/apps/roster/src/api/telesign_api.erl b/apps/roster/src/api/telesign_api.erl index 5df92803c068283643c022814ef48b1ddbe0e51f..de0a10e19024c47d42cc2bbc4b71a59c72800c1b 100644 --- a/apps/roster/src/api/telesign_api.erl +++ b/apps/roster/src/api/telesign_api.erl @@ -1,5 +1,6 @@ -% -*- coding: utf8 -*- +% -*- coding: utf-8 -*- -module(telesign_api). +-include_lib("kernel/include/logger.hrl"). -include("roster.hrl"). -export([spawned_call_sms_send/3, test_sms_sending/0, spawned_telesign_voice_call/4, telesign_voice_call/4, test_voice_call/0, send_report_to_telesign/1, send_sms/2, send_sms/3, test_jwt_generation/0, @@ -42,10 +43,10 @@ l2b(X) -> list_to_binary(X). generate_jwt(Phone) -> telesign_auto_verify_generate_token(Phone). -generate_sms(Phone) -> +generate_sms(_Phone) -> { ok, - l2b(io_lib:format("~6..0s",[i2l(crypto:rand_uniform(0,999999), 10)])) + l2b(io_lib:format("~6..0s",[i2l(rand:uniform(0,999999), 10)])) }. generate_unique_value() -> @@ -83,23 +84,23 @@ decode_jwt(JWT) -> #{ <<"xid">> := Payload } = DecodedJWT, {ok, binary_to_list(Payload)} catch - E:R -> roster:info(?MODULE, "Getting XID failed!~n~p:~p", [E,R]), + E:R -> ?LOG_INFO("Getting XID failed!~n~p:~p", [E,R]), {error, R} end; _ -> - roster:info(?MODULE, "Decoding error~n ~p:~p", [Status, DecodedJWT]), + ?LOG_INFO("Decoding error~n ~p:~p", [Status, DecodedJWT]), {error, DecodedJWT} end, {ResponseStatus, ResponseData}. parse_telesign_auto_verify_response(Response) -> try - {_, EncodedResult} = mochijson:decode(Response), - {_, EncodedResultStatusStruct} = proplists:get_value("status", EncodedResult), - ResultCode = proplists:get_value("code", EncodedResultStatusStruct), - ResultDescription = proplists:get_value("description", EncodedResultStatusStruct), + EncodedResult = jsx:decode(iolist_to_binary(Response)), + EncodedResultStatusStruct = proplists:get_value(<<"status">>, EncodedResult), + ResultCode = proplists:get_value(<<"code">>, EncodedResultStatusStruct), + ResultDescription = proplists:get_value(<<"description">>, EncodedResultStatusStruct), {ok, {ResultCode, ResultDescription}} catch - E:R -> roster:info(?MODULE, "Parsing Telesing Response Failed!~n~p:~p", [E,R]), + E:R -> ?LOG_INFO("Parsing Telesing Response Failed!~n~p:~p", [E,R]), {error, R} end. @@ -122,7 +123,7 @@ validate_language(Lang) -> %% ------------------------------------------------------------------ send_report_to_telesign(Body) -> - {_, EncodedBody} = mochijson:decode(Body), + EncodedBody = jsx:decode(Body), ReferenceId = proplists:get_value("reference_id", EncodedBody), Headers = generate_telesign_headers([], ?HTTP_PUT_METHOD, iolist_to_binary([?TELESIGN_COMPLETION_RESOURCE, "/", ReferenceId])), @@ -146,7 +147,7 @@ send_sms(PhoneNumber, MessageText, SendFun) when is_integer(PhoneNumber) -> send_sms(PhoneNumber, MessageText, SendFun) when is_binary(MessageText) -> send_sms(PhoneNumber, binary_to_list(MessageText), SendFun); send_sms(PhoneNumber, MessageText, SendFun) -> - roster:info(?MODULE, "~p:SMS ~p", [PhoneNumber, MessageText]), + ?LOG_INFO("~p:SMS ~p", [PhoneNumber, MessageText]), POSTVariables = binary_to_list(iolist_to_binary(["phone_number=", PhoneNumber, "&template=", MessageText])), Headers = generate_telesign_headers(POSTVariables, ?HTTP_POST_METHOD, ?TELESIGN_SMS_VERIFY_RESOURCE), URL = binary_to_list(iolist_to_binary([?TELESIGN_API_ENDPOINT, ?TELESIGN_SMS_VERIFY_RESOURCE])), @@ -176,7 +177,7 @@ telesign_voice_call(PhoneNumber, VerifyCode, TTSMessage, Language) when is_binar telesign_voice_call(PhoneNumber, VerifyCode, TTSMessage, Language) when is_binary(Language) -> telesign_voice_call(PhoneNumber, VerifyCode, TTSMessage, binary_to_list(Language)); telesign_voice_call(PhoneNumber, VerifyCode, TTSMessage, Language) -> - roster:info(?MODULE, "~p:Call ~p:~p", [PhoneNumber, TTSMessage, VerifyCode]), + ?LOG_INFO("~p:Call ~p:~p", [PhoneNumber, TTSMessage, VerifyCode]), ValidLanguage = validate_language(Language), POSTVariables = iolist_to_binary(["phone_number=", PhoneNumber, "&language=", ValidLanguage, "&verify_code=", VerifyCode]), POSTVariablesWithTTS = case TTSMessage of @@ -227,7 +228,7 @@ telesign_auto_verify_generate_token(PhoneNumber) -> ], Key = decode_api_key(), {ok, JWT} = jwt:encode(Algorithm, Claims, Key), - roster:info(?MODULE, "~p:JWT:~p", [PhoneNumber, binary:part(JWT,0,20)]), + ?LOG_INFO("~p:JWT:~p", [PhoneNumber, binary:part(JWT,0,20)]), {ok, JWT}. check_jwt(JWT) -> @@ -304,4 +305,4 @@ test_auto_verify_status_negative_case_2() -> %% emulate Status Not Available TeleSign Error {_, JWT} = telesign_auto_verify_generate_token(?TEST_PHONE_NUMBER), {Status, _} = check_jwt(JWT), - error == Status. \ No newline at end of file + error == Status. diff --git a/apps/roster/src/api/vox_api.erl b/apps/roster/src/api/vox_api.erl index 22d4ab440402230b4ca057522a6c56399bdaf89c..e84cc566bd17046ef572404e24a67de55c64f0f8 100644 --- a/apps/roster/src/api/vox_api.erl +++ b/apps/roster/src/api/vox_api.erl @@ -1,4 +1,5 @@ -module(vox_api). +-include_lib("kernel/include/logger.hrl"). -include("roster.hrl"). -compile(export_all). @@ -23,7 +24,7 @@ generate_random_data(Length) -> AllowedChars = "abcdefghijklmnopqrstuvwxyz1234567890", MaxLength = length(AllowedChars), lists:foldl( - fun(_, Acc) -> [lists:nth(crypto:rand_uniform(1, MaxLength), AllowedChars)] ++ Acc end, + fun(_, Acc) -> [lists:nth(rand:uniform(1, MaxLength), AllowedChars)] ++ Acc end, [], lists:seq(1, Length) ). @@ -43,11 +44,11 @@ create_voximplant_user_preprocessing(Id) -> create_participants_json(Participants) -> %% NOTE! input should be in format: [{VoxId1, PhoneNumber1, Name1}, {VoxId2, PhoneNumber2, Name2}] - lists:droplast(lists:flatten([begin - {VoxId, PhoneNumber, Name} = P, - lists:append(mochijson:encode({struct, [{"username", VoxId}, - {"phone", PhoneNumber}, {"display_name", Name}]}), ",") - end || P <- Participants])). + jsx:encode(lists:map(fun({VoxId, PhoneNumber, Name}) -> + [{<<"username">>, iolist_to_binary(VoxId)}, + {<<"phone">>, iolist_to_binary(PhoneNumber)}, + {<<"display_name">>, iolist_to_binary(Name)}] + end, Participants)). get_server_ip() -> EnvIP = application:get_env(roster, server_ip), @@ -75,8 +76,8 @@ create_voximplant_user_request(UserName, UserDisplayName, UserPassword) -> Response = case AddUserSuccess of ok -> - {_, EncodedAddUserResult} = mochijson:decode(AddUserResult), - CreatedUserId = proplists:get_value("user_id", EncodedAddUserResult), + EncodedAddUserResult = jsx:decode(iolist_to_binary(AddUserResult)), + CreatedUserId = proplists:get_value(<<"user_id">>, EncodedAddUserResult), case CreatedUserId of undefined -> {error, AddUserResult}; @@ -86,8 +87,8 @@ create_voximplant_user_request(UserName, UserDisplayName, UserPassword) -> {BindUserSuccess, BindUserResult} = roster_rest:send_request(get, {BindUserURL, []}, [], []), case BindUserSuccess of ok -> - {_, EncodedBindUserResult} = mochijson:decode(BindUserResult), - BindError = proplists:get_value("error", EncodedBindUserResult), + EncodedBindUserResult = jsx:decode(iolist_to_binary(BindUserResult)), + BindError = proplists:get_value(<<"error">>, EncodedBindUserResult), case BindError of undefined -> {ok, {[{user_name, list_to_binary(UserName)}, @@ -128,7 +129,7 @@ start_conference(Name, Participants) -> {_, ServerIP} = get_server_ip(), ConfName = iolist_to_binary(["conf_", Name]), JsonParticipants = create_participants_json(Participants), - ScriptCustomData = iolist_to_binary([<<"{\"participants\":[">>, JsonParticipants,"]}"]), + ScriptCustomData = iolist_to_binary([<<"{\"participants\":">>, JsonParticipants,"}"]), AuthParams = iolist_to_binary(["account_id=", ?VOXIMPLANT_ACCOUNT_ID, "&api_key=", ?VOXIMPLANT_API_KEY]), ConfParams = iolist_to_binary(["url=", ServerIP, "&conference_name=", ConfName, "&rule_id=", ?VOXIMPLANT_CONFERENCE_RULE_ID, "&script_custom_data=", ScriptCustomData]), RequestQueryParams = iolist_to_binary([AuthParams, "&", ConfParams]), @@ -138,17 +139,17 @@ start_conference(Name, Participants) -> Response = case StartConferenceStatus of ok -> - {_, EncodedStartConferenceResult} = mochijson:decode(StartConferenceResult), - MediaSessionAccessUrl = proplists:get_value("media_session_access_url", EncodedStartConferenceResult), + EncodedStartConferenceResult = jsx:decode(iolist_to_binary(StartConferenceResult)), + MediaSessionAccessUrl = proplists:get_value(<<"media_session_access_url">>, EncodedStartConferenceResult), case MediaSessionAccessUrl of undefined -> {error, StartConferenceResult}; _ -> - {ok, MediaSessionAccessUrl} + {ok, binary_to_list(MediaSessionAccessUrl)} end; _ -> {StartConferenceStatus, StartConferenceResult} end, - roster:info(?MODULE, "Response:~p~n", [Response]), + ?LOG_INFO("Response:~p~n", [Response]), Response. %% ------------------------------------------------------------------ @@ -171,9 +172,9 @@ test_voximplant_user_creation() -> UserName = lists:concat(["Client-", generate_random_data(6)]), UserDisplayName = UserName, UserPassword = generate_random_data(8), - roster:info(?MODULE, "UserName ~p~n", [UserName]), - roster:info(?MODULE, "UserDisplayName ~p~n", [UserDisplayName]), - roster:info(?MODULE, "UserPassword ~p~n~n", [UserPassword]), + ?LOG_INFO("UserName ~p~n", [UserName]), + ?LOG_INFO("UserDisplayName ~p~n", [UserDisplayName]), + ?LOG_INFO("UserPassword ~p~n~n", [UserPassword]), {Status, _} = create_voximplant_user_request(UserName, UserDisplayName, UserPassword), %% TODO add user deletion ok == Status. @@ -183,4 +184,4 @@ test_start_conference() -> ok == Status. test_create_participants_json() -> - create_participants_json(?TEST_PARTICIPANTS). \ No newline at end of file + create_participants_json(?TEST_PARTICIPANTS). diff --git a/apps/roster/src/micro.erl b/apps/roster/src/micro.erl index 04db6a13f350275ea84f552673f0e7730417e848..4fd3443062975559f6096d410bc852b583dc0908 100644 --- a/apps/roster/src/micro.erl +++ b/apps/roster/src/micro.erl @@ -1,5 +1,6 @@ -module(micro). -compile(export_all). +-include_lib("kernel/include/logger.hrl"). -include_lib("kvs/include/metainfo.hrl"). -include("roster.hrl"). -include("micro.hrl"). @@ -33,9 +34,7 @@ uuid_to_id(Table, Acc) -> _ -> [] end; _ -> [] - end; -uuid_to_id(Table, ?IS_UUID = Acc) -> - uuid_to_id(Table, uuid:to_binary(binary_to_list(Acc))). + end. norm_uuid(<<_:128>> = Uuid) -> list_to_binary(uuid:to_string(Uuid)); norm_uuid(?IS_UUID = Uuid) -> Uuid. @@ -50,7 +49,7 @@ phone_uuid(<<"emqttd_", _/binary>> = ClientId) -> all_accounts() -> all('LinkRoster'). all(Table) when Table == 'LinkRoster'; Table == 'LinkProfile'-> - [{Table, norm_uuid(Uuid), Id} || {Table, Uuid, Id} <- kvs:all(Table)]. + [{Table, norm_uuid(Uuid), Id} || {_T, Uuid, Id} <- kvs:all(Table)]. linked_obj(RosterId) when is_integer(RosterId) -> case kvs:get('Roster', RosterId) of @@ -67,7 +66,7 @@ phone_id(PhoneId) -> _ -> %% PhoneId is UUID in this case case kvs:get('LinkRoster', PhoneId) of {ok, #'LinkRoster'{phone_id = P}} -> P; - {error, _} -> roster:info(?MODULE, "roster ~p not found in LinkRoster", [PhoneId]), [] + {error, _} -> ?LOG_INFO("roster ~p not found in LinkRoster", [PhoneId]), [] end end. @@ -90,7 +89,7 @@ link_user(#'Profile'{phone = Phone, rosters = RosterIds}, #'Profile'{phone = Pho kvs:put(#'LinkProfile'{id = PhoneUuid, phone = Phone}), Res = lists:flatten( [case catch kvs:put(#'LinkRoster'{id = AccUuid, phone_id = roster:phone_id(Phone, RosterId)}) of - {'EXIT', Err} -> roster:error(?MODULE, "invalid roster uuid ~p for profile ~p~n~p", [AccUuid, PhoneUuid, Err]), + {'EXIT', Err} -> ?LOG_ERROR("invalid roster uuid ~p for profile ~p~n~p", [AccUuid, PhoneUuid, Err]), {error, invalid_roster}; _ -> [] end || {RosterId, #'Roster'{id = AccUuid}} <- lists:zip(RosterIds, Rosters)]), diff --git a/apps/roster/src/micro_db.erl b/apps/roster/src/micro_db.erl index a0bc0253f00209b026dc08b84b4ea1a2fc938603..55f456fcbb5c75be0ceaf1838e204ce702c19a10 100644 --- a/apps/roster/src/micro_db.erl +++ b/apps/roster/src/micro_db.erl @@ -1,6 +1,7 @@ -module(micro_db). -compile(export_all). +-include_lib("kernel/include/logger.hrl"). -include("roster.hrl"). -include("micro.hrl"). %%-include_lib("emqttd/include/emqttd.hrl"). @@ -14,7 +15,7 @@ to_rec(Table, [H|T], Acc) when is_map(H)-> to_rec(Table, [H|_] = File) when is_integer(H) -> case file:read_file(File) of {ok, Json} -> to_rec(Table, Json); - {error, _} = E -> roster:info(?MODULE, "read file ~p error: ~p", [File, E]), E + {error, _} = E -> ?LOG_INFO("read file ~p error: ~p", [File, E]), E end; to_rec(Table, Json) when is_binary(Json) -> diff --git a/apps/roster/src/processes/job.erl b/apps/roster/src/processes/job.erl index dc6ec9dfce25f7c44da44987d3b20629933e120a..6418d14de4cb407113f435bb2acc899182f9c4cf 100644 --- a/apps/roster/src/processes/job.erl +++ b/apps/roster/src/processes/job.erl @@ -25,11 +25,11 @@ action({request,'Stop'}, Proc) -> io:format("Stop Process~n"), {reply,'Action',Proc}; -action({request,'Timeout'}, #process{id=Id}=Proc) -> +action({request,'Timeout'}, #process{id=_Id}=Proc) -> io:format("Timeout Process~n"), %#process{options=Opts}=bpe:load(Id), case bpe:doc(#'Schedule'{},Proc) of - #'Schedule'{id=Current, state=TRef}=Sh -> timer:cancel(TRef); + #'Schedule'{state=TRef} -> timer:cancel(TRef); _ -> skip end, % TRef= proplists:get_value(timer,Opts,undefined), %, @@ -40,7 +40,7 @@ action({request,'Timeout'}, #process{id=Id}=Proc) -> %% io:format("Event Process~n"), %% {reply,Proc}; -action({request,'Action'}, #process{id=Id, options=Opts}=Proc) -> +action({request,'Action'}, #process{}=Proc) -> io:format("Action Process ~n"), %C= proplists:get_value(send_pid,Opts,undefined), CTimeout= case lists:keytake(timeoutEvent,1,bpe:events(Proc)) of @@ -56,12 +56,12 @@ action({request,'Action'}, #process{id=Id, options=Opts}=Proc) -> T when T =<3*?QUANT -> %io:format("Pub ~p, ~p ~n", [Id,Timeout]), catch n2o_async:pid(system,roster_bpe) ! {publish, Proc}, {reply,'Stop',Proc}; - T -> catch n2o_async:pid(system,roster_bpe) ! {timeout, Sh, Timeout}, + _T -> catch n2o_async:pid(system,roster_bpe) ! {timeout, Sh, Timeout}, {reply,'Timeout',Proc} end; _ -> {reply,'Stop',Proc} end; -action({request,'Final'}, #process{id=Id, options=Opt}=Proc) -> +action({request,'Final'}, #process{}=Proc) -> io:format(" Final~n"), %roster:send_event(C, <<>>, <<>>, #'Job'{id=Id, status=delete}), %n2o_async:pid(system,roster_bpe) ! #'Job'{id=Id, status=delete}, @@ -79,28 +79,28 @@ worker(#process{id=Id}=P) -> __ -> skip end . -worker_do({Days,Time},P) when Days >= 14 -> skip; -worker_do({Days,Time},#process{id=Id}=P) when P#process.task =:= 'Action' -> +worker_do({Days,_Time},_P) when Days >= 14 -> skip; +worker_do({_Days,_Time},#process{id=Id}=P) when P#process.task =:= 'Action' -> % catch n2o_async:pid(system,roster_bpe) ! {restart, P}, bpe:start(P, []), %bpe:complite(Id), kvs:info(?MODULE,"BPE Start: ~p~n",[Id]); -worker_do({Days,Time},#process{id=Id, options = Ops}=P) when P#process.task =:= 'Stop' -> +worker_do({_Days,_Time},#process{id=Id}=P) when P#process.task =:= 'Stop' -> bpe:start(P, []), % bpe:complite(Id), io:format("BPE Start: ~p~n",[Id]); -worker_do({Days,Time},#process{id=Id}=P) when P#process.task =:= 'Init' -> +worker_do({_Days,_Time},#process{id=Id}=P) when P#process.task =:= 'Init' -> bpe:start(P, []), % bpe:complite(Id), kvs:info(?MODULE,"BPE Start: ~p~n",[Id]); %%worker_do({Days,Time},P) when P#process.task =:= 'Final' -> kvs:info(?MODULE,"BPE Start: ~p~n",[bpe:start(P,[])]); %%worker_do({Days,Time},P) when P#process.task =:= 'FirstDelay' -> kvs:info(?MODULE,"BPE Start: ~p~n",[bpe:start(P,[])]); -worker_do({Days,Time},#process{id=Id}=P) when P#process.task =:= 'Timeout' -> +worker_do({_Days,_Time},#process{id=Id}=P) when P#process.task =:= 'Timeout' -> bpe:start(P, []), kvs:info(?MODULE,"BPE Start: ~p~n",[Id]); % bpe:complite(Id); -worker_do({Days,Time},P) ->kvs:info(?MODULE,"BPE Start: ~p~n",[P]). %skip. +worker_do({_Days,_Time},P) ->kvs:info(?MODULE,"BPE Start: ~p~n",[P]). %skip. next(Fun,R,Current,Acc) -> @@ -126,7 +126,7 @@ next(R,Current) -> clear_schedule()-> [kvs:delete('Schedule', Time) || #'Schedule'{id=Time} <- kvs:all('Schedule'), Time < roster:now_msec()]. -clear_hist(#process{id=Id}=P)-> case bpe:hist(Id) of - [H|T] -> [kvs:remove(hist,I) || #'hist'{id=I} <- T]; +clear_hist(#process{id=Id})-> case bpe:hist(Id) of + [_|T] -> [kvs:remove(hist,I) || #'hist'{id=I} <- T]; __ -> skip end. diff --git a/apps/roster/src/processes/job_process.erl b/apps/roster/src/processes/job_process.erl index 4662dec78862b63eedcea13eb04632a57630f02e..653afbf4708aaee8a459244db628cfa417b18e17 100644 --- a/apps/roster/src/processes/job_process.erl +++ b/apps/roster/src/processes/job_process.erl @@ -85,6 +85,6 @@ update_opt(#process{options = Opts}=P,Opt)-> . get_proc(Proc,Key) -> - P=#process{options = Opts}=bpe:load(Proc), + #process{options = Opts}=bpe:load(Proc), case proplists:get_value(Key,Opts,undefined) of T when is_integer(T) -> T; _ -> 0 end . diff --git a/apps/roster/src/protocol/micro_auth.erl b/apps/roster/src/protocol/micro_auth.erl index 00a0ef818f8816c38b1ce6f11c03e0bae69a22d2..ef107d066544e35d8b278e24b4fc49bc9638176d 100644 --- a/apps/roster/src/protocol/micro_auth.erl +++ b/apps/roster/src/protocol/micro_auth.erl @@ -1,4 +1,5 @@ -module(micro_auth). +-include_lib("kernel/include/logger.hrl"). -include("roster.hrl"). -include("micro.hrl"). -include("static_auth.hrl"). @@ -18,23 +19,23 @@ init([Listeners]) -> {ok, Listeners}. description() -> "Micro Authentication Module". check(#mqtt_client{client_id = <<"sys_micro_", _/binary>> = ClientId, client_pid = ClientPid}, _Pwd, _State) -> - roster:info(?MODULE, "~p:Auth:auth(micro)/check", [ClientId]), - emqttd_client:subscribe(ClientPid, [{roster:action_topic(ClientId), 2}]), + ?LOG_INFO("~p:Auth:auth(micro)/check", [ClientId]), + emqttd_client:subscribe(ClientPid, [{roster:action_topic(ClientId), 2}]), ignore; -check(#mqtt_client{client_id = <<"emqttd_", _/binary>> = ClientId, +check(#mqtt_client{client_id = <<"emqttd_", _/binary>> = ClientId, username = <<"micro">>, ws_initial_headers = [_|_] = Headers} = MC, Pwd, State) when not is_tuple(Pwd) -> - roster:info(?MODULE, "~p:Auth:auth(micro)/check/ws headers: ~p", [ClientId, Headers]), + ?LOG_INFO("~p:Auth:auth(micro)/check/ws headers: ~p", [ClientId, Headers]), Jwt = get_token(normalize(Headers)), check(MC, {token, Jwt}, State); -check(#mqtt_client{client_id = <<"emqttd_", _/binary>> = ClientId, +check(#mqtt_client{client_id = <<"emqttd_", _/binary>>, username = <<"micro">>} = MC, ?SYS_TOKEN=Pwd, State) -> -check(MC, {token, Pwd}, State); -check(#mqtt_client{client_id = <<"emqttd_", _/binary>> = ClientId, + check(MC, {token, Pwd}, State); +check(#mqtt_client{client_id = <<"emqttd_", _/binary>> = ClientId, username = <<"micro">>, - client_pid = ClientPid, + client_pid = ClientPid, will_topic = WT}, {token, Token}, _State) -> - roster:info(?MODULE, "~p:Auth:auth(micro)/check", [ClientId]), + ?LOG_INFO("~p:Auth:auth(micro)/check", [ClientId]), case WT of <<"version/", BVer/binary>> -> M = application:get_env(roster, validate_token, ?MODULE), @@ -48,42 +49,45 @@ check(#mqtt_client{client_id = <<"emqttd_", _/binary>> = ClientId, {ok, #'Auth'{user_id = PhoneId, type = logout} = Auth0} -> Auth1 = Auth0#'Auth'{last_online = roster:now_msec(), type = verified}, kvs:put(Auth1), - roster:info(?MODULE, "~p:Auth:auth(micro)/check:session created, post logout ", [ClientId]), + ?LOG_INFO("~p:Auth:auth(micro)/check:session created, post logout ", [ClientId]), + roster:sub_client(subscribe, ClientId, PhoneId), AuthPid ! roster_auth:control_ver(Auth1, Ver), ok; {ok, #'Auth'{user_id = PhoneId} = Auth} -> + roster:sub_client(subscribe, ClientId, PhoneId), AuthPid ! roster_auth:control_ver(Auth#'Auth'{type = []}, Ver), ok; {ok, #'Auth'{user_id = _PhoneId2} = Auth} -> - roster:info(?MODULE, "invalid account: ~p", [Auth]), + ?LOG_INFO("invalid account: ~p", [Auth]), {error, invalid_account_id}; {error, _} -> case PhoneId of - [] -> roster:info(?MODULE, "account not found: ~p", [AccUuid]), + [] -> ?LOG_INFO("account not found: ~p", [AccUuid]), {error, roster_not_found}; _ -> kvs:put(Auth = #'Auth'{client_id = ClientId, user_id = PhoneId, phone = roster:phone(PhoneId), created = roster:now_msec(), last_online = roster:now_msec(), type = verified}), - roster:info(?MODULE, "~p:Auth:auth(micro)/check:session created", [ClientId]), + ?LOG_INFO("~p:Auth:auth(micro)/check:session created", [ClientId]), + roster:sub_client(subscribe, ClientId, PhoneId), AuthPid ! roster_auth:control_ver( Auth#'Auth'{client_id = ClientId, user_id = PhoneId, type = verified}, Ver), ok end end; error -> - roster:info(?MODULE, "~p:Auth:auth(micro)/check:invalid_token", [ClientId]), + ?LOG_INFO("~p:Auth:auth(micro)/check:invalid_token", [ClientId]), {error, invalid_token} end; _ -> {error, invalid_version} end; -check(#mqtt_client{client_id = <<"emqttd_", _/binary>> = ClientId, username = Username}, _, State) when Username /= <<"api">>, Username /= <<"micro">> -> - roster:info(?MODULE, "~p:Auth:auth(micro)/check:invalid_username: ~p", [ClientId, Username]), +check(#mqtt_client{client_id = <<"emqttd_", _/binary>> = ClientId, username = Username}, _,_State) when Username /= <<"api">>, Username /= <<"micro">> -> + ?LOG_INFO("~p:Auth:auth(micro)/check:invalid_username: ~p", [ClientId, Username]), {error, invalid_username}; check(_Client, _Password, _Opts) -> ignore. info(#'Auth'{type = update, client_id = <<"emqttd_", _/binary>> = ClientId, token = Token, settings = Settings} = Auth, Req, #cx{params = <<"sys_", _/binary>>, client_pid = C} = State) -> - roster:info(?MODULE, "~p:Auth/update(micro):~p", [ClientId, Auth]), + ?LOG_INFO("~p:Auth/update(micro):~p", [ClientId, Auth]), IO = case kvs:get('Auth', ClientId) of {ok, #'Auth'{type = Type} = StoredAuth} -> %% TODO merge or set settings? SendAuth = StoredAuth#'Auth'{token = Token, settings = Settings, type = update}, @@ -102,7 +106,7 @@ info(#'Auth'{type = delete, client_id = <<"emqttd_", _/binary>> = ClientId} = Au info(#'Auth'{type = logout, client_id = <<"emqttd_", _/binary>> = ClientId} = Auth, Req, #cx{params = SysClientId, client_pid = C} = State) -> IO = case kvs:get('Auth', ClientId) of {ok, #'Auth'{} = StoredAuth} -> - roster_presence:on_disconnect(A = StoredAuth#'Auth'{type = logout}, C), + roster_presence:on_disconnect(StoredAuth#'Auth'{type = logout}, C), roster:send_action(C, SysClientId, Auth), %%TODO send uuid phone and roster_id to micro? <<>>; {error, _} -> #io{code = #error{code = session_not_found}} @@ -111,18 +115,18 @@ info(#'Auth'{type = logout, client_id = <<"emqttd_", _/binary>> = ClientId} = Au info(#'Auth'{phone = Phone} = Auth, Req, #cx{params = ClientId} = State) -> - roster:info(?MODULE, "~p:~p:Auth/unknown(micro):~p", [Phone, ClientId, Auth]), + ?LOG_INFO("~p:~p:Auth/unknown(micro):~p", [Phone, ClientId, Auth]), {reply, {bert, #io{code = #error{code = invalid_data}}}, Req, State}. %% micro_auth handlers proc(init, #handler{name = micro_auth} = Async) -> {ok, C} = emqttc:start_link([{client_id, <<"micro_auth">>}, {logger, {console, error}}, {reconnect, 5}]), - roster:info(?MODULE, "ASYNC AUTH PROC started", []), + ?LOG_INFO("ASYNC AUTH PROC started", []), {ok, Async#handler{state = C, seq = 0}}; proc(#'Auth'{type = {ver, Ver}, phone = Phone, client_id = <<"emqttd_", _/binary>> = ClientId}, #handler{state = C} = H) -> - roster:info(?MODULE, "~p:~p:Auth(micro)/login", [Phone, ClientId]), + ?LOG_INFO("~p:~p:Auth(micro)/login", [Phone, ClientId]), catch roster_presence:on_connect(Phone, ClientId, C, Ver), %%TODO send Auth updated session? {reply, [], H}; @@ -130,9 +134,10 @@ proc({disconnect, #'Auth'{client_id = <<"emqttd_", _/binary>>} = Auth}, #handler{state = C} = H) -> roster_presence:on_disconnect(Auth, C), {reply, [], H}; -proc(#'Auth'{type = {reg, Ver}, client_id = <<"emqttd_", _/binary>> = ClientId, phone = Phone, user_id = UserId} = Stored, +proc(#'Auth'{type = {reg, Ver}, client_id = <<"emqttd_", _/binary>> = ClientId, + phone = Phone, user_id = UserId}, #handler{state = C} = H) -> - roster:info(?MODULE, "~p:~p:Auth(micro)/login(created session)", [Phone, ClientId]), + ?LOG_INFO("~p:~p:Auth(micro)/login(created session)", [Phone, ClientId]), roster_presence:on_verify(ClientId, UserId), roster_presence:on_connect(Phone, ClientId, C, Ver), {reply, [], H}; @@ -140,7 +145,7 @@ proc(#'Auth'{type = {reg, Ver}, client_id = <<"emqttd_", _/binary>> = ClientId, proc({mqttc, C, connected}, State = #handler{state = C, seq = S}) -> {ok, State#handler{seq = S + 1}}; proc({mqttc, _C, disconnected}, State) -> {ok, State}; proc(Unknown, #handler{} = H) -> - roster:info(?MODULE, "invalid auth data :~p", [Unknown]), + ?LOG_INFO("invalid auth data :~p", [Unknown]), {reply, [], H}. %% JWT parser without public key @@ -150,7 +155,7 @@ validate_token(Token) -> #{<<"sub">> := Sub} = Payload, {ok, base64:decode(Sub)} catch Err:Rea -> - roster:error(?MODULE,"Catch invalid token :~p~n",[n2o:stack_trace(Err,Rea)]), + ?LOG_ERROR("Catch invalid token :~p~n",[n2o:stack_trace(Err,Rea)]), error end. normalize(Headers) -> normalize(Headers, []). @@ -163,7 +168,7 @@ get_token(Headers) -> ?EMPTY_TOKEN -> case proplists:get_value("x-envoy-original-path", Headers, ?EMPTY_TOKEN) of %% If empty, try getting tokens from params headers ?EMPTY_TOKEN -> ?EMPTY_TOKEN; - Params -> + Params -> <<"/mqtts?access_token=", Jwt/binary>> = list_to_binary(Params), %% Parse the token from the params Jwt end; @@ -219,4 +224,4 @@ get_token(Headers) -> %% case kvs:get('LinkRoster', AccId) of %% {ok, #'LinkRoster'{phone_id = PhoneId}} -> roster:roster_id(PhoneId); %% _ -> [] -%% end. \ No newline at end of file +%% end. diff --git a/apps/roster/src/protocol/micro_profile.erl b/apps/roster/src/protocol/micro_profile.erl index 64b9094c4b5ed1d401d37f738afda6055d72fab2..120a941d0150287c56cb280a3512ba5d36e5a049 100644 --- a/apps/roster/src/protocol/micro_profile.erl +++ b/apps/roster/src/protocol/micro_profile.erl @@ -1,4 +1,5 @@ -module(micro_profile). +-include_lib("kernel/include/logger.hrl"). -include("roster.hrl"). -include("micro.hrl"). -include_lib("n2o/include/n2o.hrl"). @@ -7,10 +8,10 @@ info(#'Profile'{phone=LinkPhone, rosters=Rosters, status=create, settings = Settings} = Profile, Req, %% TODO are Rosters sent? #cx{params = ClientId = <<"sys_", _/binary>>, client_pid = C} = State) -> Phone = micro:uuid_to_id('LinkProfile', LinkPhone), - roster:info(?MODULE, "~p:~p:Profile/create:~s", [LinkPhone,ClientId,Profile]), + ?LOG_INFO("~p:~p:Profile/create:~s", [LinkPhone,ClientId,Profile]), IO2 = case kvs:get('Profile', Phone) of {ok, #'Profile'{}} -> - roster:info(?MODULE, "~p:~p:Profile/already_exist", [LinkPhone, ClientId]), + ?LOG_INFO("~p:~p:Profile/already_exist", [LinkPhone, ClientId]), #io{code = #error{code = already_exist}, data = LinkPhone}; _ -> OldPhone = roster:get_data_val(<<"PHONE">>, Settings), @@ -51,13 +52,13 @@ info(#'Profile'{phone=LinkPhone, rosters=Rosters, status=create, settings = Sett end end end, - roster:info(?MODULE, "~p:~p:Profile/create.result:~p", [LinkPhone,ClientId, IO2]), + ?LOG_INFO("~p:~p:Profile/create.result:~p", [LinkPhone,ClientId, IO2]), {reply, {bert, IO2}, Req, State}; info(#'Profile'{phone=LinkPhone, rosters=[], status = update} = Profile, Req, #cx{params = ClientId = <<"sys_", _/binary>>, client_pid = C} = State) -> Phone = micro:uuid_to_id('LinkProfile', LinkPhone), - roster:info(?MODULE, "~p:~p:Profile(micro)/update:~s", [LinkPhone,ClientId,io_lib:format("~p",[Profile])]), + ?LOG_INFO("~p:~p:Profile(micro)/update:~s", [LinkPhone,ClientId,io_lib:format("~p",[Profile])]), IO = case kvs:get('Profile', Phone) of {ok, #'Profile'{phone = Phone} = StoredProfile} -> P = roster:patch_profile(StoredProfile, Profile), @@ -70,12 +71,12 @@ info(#'Profile'{phone=LinkPhone, rosters=[], status = update} = Profile, Req, {reply, {bert, IO}, Req, State}; info(#'Profile'{status = remove, phone=UUID, rosters =Accounts }=Data, Req, - #cx{params = ClientId= <<"sys_", _/binary>>, client_pid = C} = State) -> - roster:info(?MODULE, "~p:~p:Accounts/remove:~s", [UUID,ClientId,io_lib:format("~p",[Data])]), + #cx{params = ClientId= <<"sys_", _/binary>>} = State) -> + ?LOG_INFO("~p:~p:Accounts/remove:~s", [UUID,ClientId,io_lib:format("~p",[Data])]), Rosters = lists:flatten([begin [_,RId]=roster:parts_phone_id(micro:uuid_to_id('LinkRoster',A)), RId end || A <- Accounts]), UID = micro:uuid_to_id('LinkProfile',UUID), case roster_profile:info(#'Profile'{status = delete, phone=UID, rosters = [_|_]=Rosters}, Req,State) of - {reply, {bert, {io, {error, R}, <<>>}}, Req, State}=E -> E; + {reply, {bert, {io, {error, _R}, <<>>}}, Req, State}=E -> E; {reply, {bert, P=#'Profile'{phone=UID, rosters = NewRosters}}, Req, State} -> Accs=[ begin kvs:delete('LinkRoster', Id), micro:norm_uuid(Id) end || RosterId <- Rosters--NewRosters, #'LinkRoster'{id=Id} <- kvs:index('LinkRoster',phone_id,roster:phone_id(UID,RosterId))], @@ -84,5 +85,5 @@ info(#'Profile'{status = remove, phone=UUID, rosters =Accounts }=Data, Req, info(#'Profile'{phone = LinkPhone} = Profile, Req, #cx{params = ClientId} = State) -> - roster:info(?MODULE, "~p:Profile/unknown:~p", [ClientId, Profile]), + ?LOG_INFO("~p:Profile/unknown:~p", [ClientId, Profile]), {reply, {bert, #io{code = #error{code = invalid_data}, data = LinkPhone}}, Req, State}. diff --git a/apps/roster/src/protocol/micro_roster.erl b/apps/roster/src/protocol/micro_roster.erl index 29762e223ff21f50e41b26a6adbc2b9968143cac..9468a61d93af17611acf85ef9e674fa23ae38e3f 100644 --- a/apps/roster/src/protocol/micro_roster.erl +++ b/apps/roster/src/protocol/micro_roster.erl @@ -1,4 +1,5 @@ -module(micro_roster). +-include_lib("kernel/include/logger.hrl"). -include("roster.hrl"). -include("micro.hrl"). -include_lib("n2o/include/n2o.hrl"). @@ -10,7 +11,7 @@ info(#'Roster'{status = add, phone = LinkPhone, nick = Nick, id = LinkRosterId} #cx{params = <<"sys_", _/binary>> = ClientId} = State) -> Phone = micro:uuid_to_id('LinkProfile', LinkPhone), PhoneId = micro:uuid_to_id('LinkRoster', LinkRosterId), - roster:info(?MODULE, "~p:~p:Roster(micro)/add:~s", [LinkRosterId, ClientId, io_lib:format("~p", [Roster])]), + ?LOG_INFO("~p:~p:Roster(micro)/add:~s", [LinkRosterId, ClientId, io_lib:format("~p", [Roster])]), IO = case kvs:get('Profile', Phone) of {ok, #'Profile'{rosters = RosterIds} = Profile} -> RosterId = roster:roster_id(PhoneId), @@ -42,7 +43,7 @@ info(#'Roster'{status = add, phone = LinkPhone, nick = Nick, id = LinkRosterId} info(#'Roster'{status = update, id = LinkRosterId, nick = Nick} = Roster, Req, #cx{params = <<"sys_", _/binary>> = ClientId} = State) when Nick /= []-> PhoneId = micro:uuid_to_id('LinkRoster', LinkRosterId), - roster:info(?MODULE, "~p:~p:Roster(micro)/update:~s", [LinkRosterId, ClientId, io_lib:format("~p", [Roster])]), + ?LOG_INFO("~p:~p:Roster(micro)/update:~s", [LinkRosterId, ClientId, io_lib:format("~p", [Roster])]), IO2 = case kvs:get('Roster', RosterId = roster:roster_id(PhoneId)) of {ok, #'Roster'{}} -> @@ -60,6 +61,6 @@ info(#'Roster'{status = update, id = LinkRosterId, nick = Nick} = Roster, Req, end, {reply, {bert, IO2}, Req, State}; info(#'Roster'{} = Roster, Req, #cx{params = <<"sys_", _/binary>> = ClientId} = State) -> - roster:info(?MODULE, "~p:Profile(micro)/unknown:~p", [ClientId, Roster]), + ?LOG_INFO("~p:Profile(micro)/unknown:~p", [ClientId, Roster]), {reply, {bert, #io{code = #error{code = invalid_data}}}, Req, State}. diff --git a/apps/roster/src/protocol/roster_acl.erl b/apps/roster/src/protocol/roster_acl.erl index 570bea6ef9a2b58ad953289146fff8d0f8ffd387..c07721aaa68d32fcb75e2e50c53cbe76f4227bb8 100644 --- a/apps/roster/src/protocol/roster_acl.erl +++ b/apps/roster/src/protocol/roster_acl.erl @@ -72,13 +72,13 @@ check_acl({#mqtt_client{client_id = <<"emqttd_", _/binary>> = Client}, publish, allow; check_acl({#mqtt_client{client_id = <<"emqttd_", _/binary>>}, publish, <<"auth/", _/binary>>}, #state{}) -> deny; -check_acl({#mqtt_client{client_id = <<"emqttd_", _/binary>> = Client}, publish, <<"events/", _/binary>>} = Who, #state{}) -> +check_acl({#mqtt_client{client_id = <<"emqttd_", _/binary>> = Client}, publish, <<"events/", _/binary>>}, #state{}) -> case kvs:get('Auth', Client) of {ok, #'Auth'{type = expired}} -> deny; {ok, _} -> allow; {error, _} -> deny end; -check_acl({Client, PubSub, Topic} = Who, #state{nomatch = Default}) -> +check_acl({Client, PubSub, Topic}, #state{nomatch = Default}) -> case match(Client, Topic, lookup(PubSub)) of {matched, allow} -> allow; {matched, deny} -> deny; @@ -116,4 +116,4 @@ reload_acl() -> %% @doc ACL Module Description -spec(description() -> string()). description() -> - "Roster ACL with etc/acl.conf". \ No newline at end of file + "Roster ACL with etc/acl.conf". diff --git a/apps/roster/src/protocol/roster_auth.erl b/apps/roster/src/protocol/roster_auth.erl index dadb3d88e4d548cf2a276527f1fd6d1899ba463f..b0fad914e410eeee90639ccc94ded9589defc343 100644 --- a/apps/roster/src/protocol/roster_auth.erl +++ b/apps/roster/src/protocol/roster_auth.erl @@ -1,4 +1,5 @@ -module(roster_auth). +-include_lib("kernel/include/logger.hrl"). -include("roster.hrl"). -include("static_auth.hrl"). %-include_lib("bpe/include/bpe.hrl"). @@ -20,14 +21,14 @@ start() -> stop() -> n2o_async:stop(system, roster_auth). -init([Listeners]) -> - roster:info(?MODULE, "init/2: Listeners: ~p", [Listeners]), +init([Listeners]) -> + ?LOG_INFO("init/2: Listeners: ~p", [Listeners]), {ok, Listeners}. description() -> "Roster Authentication Module". %%check(#mqtt_client{client_id = <<"nagios-", _/binary>> = ClientId, username = <<"api">>}, _, _) -> -%% roster:info(?MODULE, "~p:Nagios/test", [ClientId]), +%% ?LOG_INFO("~p:Nagios/test", [ClientId]), %% ignore; check(#mqtt_client{ @@ -36,17 +37,16 @@ check(#mqtt_client{ client_pid = ClientPid, will_topic = <<"version/", _BVer/binary>> }, _, _) -> - roster:info(?MODULE, "~p:Auth:reg/check", [ClientId]), + ?LOG_INFO("~p:Auth:reg/check", [ClientId]), emqttd_client:subscribe(ClientPid, [{roster:action_topic(ClientId), 2}]), ignore; check(#mqtt_client{ client_id = <<"reg_", _/binary>> = ClientId, username = <<"api">>, - client_pid = ClientPid, will_topic = _WT }, _, _) -> - roster:error(?MODULE, "~p:Auth:reg/check:error:version ~s", [ClientId, _WT]), + ?LOG_ERROR("~p:Auth:reg/check:error:version ~s", [ClientId, _WT]), {error, invalid_version}; check(#mqtt_client{ @@ -55,7 +55,7 @@ check(#mqtt_client{ }, undefined, _State) -> - roster:error(?MODULE, "~p:Auth:auth/check:error:token undefined", [ClientId]), + ?LOG_ERROR("~p:Auth:auth/check:error:token undefined", [ClientId]), {error, invalid_token}; check(#mqtt_client{ @@ -72,11 +72,11 @@ check(#mqtt_client{ token = Token, settings = Settings, last_online = LO - } = Auth) when Type == verified; + } = Auth) when Type == verified; Type == []; Type == expired -> - roster:info(?MODULE, "[WS Headers]: ~p", [Headers]), - roster:info(?MODULE, "~p:Auth:auth/check:token_equal:~p~n~p", [ClientId, WT, binary:part(Token,0,min(16,size(Token)))]), + ?LOG_INFO("[WS Headers]: ~p", [Headers]), + ?LOG_INFO("~p:Auth:auth/check:token_equal:~p~n~p", [ClientId, WT, binary:part(Token,0,min(16,size(Token)))]), emqttd_client:subscribe(ClientPid, [{roster:action_topic(ClientId), 2}]), IpF = #'Feature'{ id = <>, @@ -94,9 +94,9 @@ check(#mqtt_client{ ), AuthPid = roster:n2o_pid(?MODULE), Now = roster:now_msec(), - LastOnline = case Type of + LastOnline = case Type of verified -> Now; - _ -> LO + _ -> LO end, Auth2 = Auth#'Auth'{ type = update, @@ -108,9 +108,9 @@ check(#mqtt_client{ {bad_token,_} -> Auth3 = Auth2#'Auth'{ type = expired, - token = element(2,roster:gen_token([],[])) + token = element(2,roster:gen_token([],[])) }, - roster:info(?MODULE, "Auth:auth/roamToken:~p", [Auth3]), + ?LOG_INFO("Auth:auth/roamToken:~p", [Auth3]), kvs:put(Auth3), AuthPid ! Auth3#'Auth'{ type = update }, ok; @@ -126,7 +126,7 @@ check(#mqtt_client{ {_, _} -> OldIpBin = case FindIp = lists:keyfind(?IP_KEY, #'Feature'.key, Settings) of false -> IpBin; - #'Feature'{value = V} -> V + #'Feature'{value = V} -> V end, IsCheckIp = application:get_env(roster, auth_check_ip, true), Ver = binary_to_list(BVer), @@ -155,11 +155,11 @@ check(#mqtt_client{ token = Tok, phone = Phone } = Auth) -> - roster:info(?MODULE, "~p:Auth:auth/check:token_differ~p~n~p", [ClientId, WT, binary:part(Token, 0, min(16, size(Token)))]), + ?LOG_INFO("~p:Auth:auth/check:token_differ~p~n~p", [ClientId, WT, binary:part(Token, 0, min(16, size(Token)))]), case roster:parse_token2(Token) of {error, invalid_token} -> - roster:error(?MODULE, "Auth:auth/check:token:~p.InvalidToken:~p", [binary:part(Tok, 0, 16), Token]), + ?LOG_ERROR("Auth:auth/check:token:~p.InvalidToken:~p", [binary:part(Tok, 0, 16), Token]), {error, mismatch_user_data}; OldTok -> case binary:part(Tok, 0, 10) == OldTok orelse roster:parse_token2(Tok) == OldTok of @@ -170,12 +170,12 @@ check(#mqtt_client{ ) ) of true -> []; - _ -> verified + _ -> verified end, kvs:put(Auth#'Auth'{token = Token, type = Type}), check(MqttClient, Token, Auth#'Auth'{token = Token, type = Type}); _ -> - roster:error(?MODULE, "Auth:auth/check:token:~p.InvalidOldToken:~p", [binary:part(Tok, 0, 16), OldTok]), + ?LOG_ERROR("Auth:auth/check:token:~p.InvalidOldToken:~p", [binary:part(Tok, 0, 16), OldTok]), {error, mismatch_user_data} end end; @@ -183,44 +183,40 @@ check(#mqtt_client{ check(#mqtt_client{ client_id = <<"emqttd_", _/binary>> = ClientId, username = <<"api">>, - client_pid = ClientPid, - will_topic = <<"version/", _BVer/binary>> = WT, - ws_initial_headers = Headers + will_topic = <<"version/", _BVer/binary>> = WT } = MqttClient, Token, State) -> - roster:info(?MODULE, "~p:Auth:auth/check:getting_auth:~p~n~p", [ClientId, WT, binary:part(Token,0,min(16,size(Token)))]), + ?LOG_INFO("~p:Auth:auth/check:getting_auth:~p~n~p", [ClientId, WT, binary:part(Token,0,min(16,size(Token)))]), %%%% case maybe_get_auth(State, ClientId) of {ok, Auth} -> check(MqttClient, Token, Auth); {error, not_found} = Error404 -> - roster:error(?MODULE, "~p:Auth/check:~p", [ClientId, Error404]), + ?LOG_ERROR("~p:Auth/check:~p", [ClientId, Error404]), Error404; - Err -> - roster:error(?MODULE, "~p:Auth:auth/check:~p", [ClientId, Err]), - Err + Err -> + ?LOG_ERROR("~p:Auth:auth/check:~p", [ClientId, Err]), + Err end; check(#mqtt_client{ client_id = <<"emqttd_", _/binary>> = ClientId, username = <<"api">>, - client_pid = _ClientPid, will_topic = _WT, - peername = {Ip, _}, - ws_initial_headers = Headers - } = MC, + peername = {_Ip, _} + }, _Token, _State) -> - roster:error(?MODULE, "~p:Auth:reg/check:error:version ~s", [ClientId, _WT]), + ?LOG_ERROR("~p:Auth:reg/check:error:version ~s", [ClientId, _WT]), {error, invalid_version}; check(#mqtt_client{ client_id = <<"emqttd_", _/binary>> = ClientId, username = Username }, _, - State) when Username /= <<"api">>, + _State) when Username /= <<"api">>, Username /= <<"micro">> -> - roster:info(?MODULE, "~p:Auth:auth/check:invalid_username: ~p", [ClientId, Username]), + ?LOG_INFO("~p:Auth:auth/check:invalid_username: ~p", [ClientId, Username]), {error, invalid_username}; check(_Client, _Password, _Opts) -> ignore. @@ -243,7 +239,7 @@ info(#'Auth'{ phone = Phone, client_id = RegClientId }), - roster:info(?MODULE, "~p:~p:Auth/fake", [Phone, RegClientId]), + ?LOG_INFO("~p:~p:Auth/fake", [Phone, RegClientId]), info(Auth2#'Auth'{ type = verify, token = [] @@ -267,7 +263,7 @@ info(#'Auth'{ {error, _} -> skip; {ok, #'Auth'{} = A} -> - roster_presence:on_disconnect(A#'Auth'{type = logout}, C) + roster_presence:on_disconnect(A#'Auth'{type = logout}, C) end; {ok, #'Auth'{client_id = ClId}} -> kvs:delete('Auth', ClId) @@ -275,7 +271,7 @@ info(#'Auth'{ {reply, {bert, #io{ code = case check_fake_numbers(Phone) of {ok, _} -> - roster:info(?MODULE, "~p:~p:Auth/reg.FakeNumber", [Phone, RegClientId]), + ?LOG_INFO("~p:~p:Auth/reg.FakeNumber", [Phone, RegClientId]), kvs:put(Auth#'Auth'{ sms_code = ?FAKE_SMS, attempts = ?MAX_ATTEMPTS, @@ -285,12 +281,12 @@ info(#'Auth'{ _ -> case check_whitelist(Phone) of {error, _} -> - roster:info(?MODULE, "~p:~p:Auth/reg.NotAllowed", [Phone, RegClientId]), + ?LOG_INFO("~p:~p:Auth/reg.NotAllowed", [Phone, RegClientId]), #error{code = number_not_allowed}; {ok, _} -> Fun = case Services of [] -> generate_sms; - [jwt] -> generate_jwt + [jwt] -> generate_jwt end, {ok, Codes} = telesign_api:Fun(Phone), kvs:put(Auth#'Auth'{ @@ -298,7 +294,7 @@ info(#'Auth'{ attempts = ?MAX_ATTEMPTS, client_id = RegClientId }), - roster:info(?MODULE, + ?LOG_INFO( "~p:~p:Auth/reg~p:~p", [ Phone, @@ -325,7 +321,7 @@ info(#'Auth'{ }, Req, #cx{params = <<"reg_", _/binary>> = RegClientId} = State) when DevKey /= [] -> - roster:info(?MODULE, "~p:Auth/resend", [RegClientId]), + ?LOG_INFO("~p:Auth/resend", [RegClientId]), {reply, {bert, #io{ code = case kvs:get('Auth', RegClientId) of {ok, #'Auth'{ @@ -360,7 +356,7 @@ info(#'Auth'{ }, Req, #cx{params = <<"reg_", _/binary>> = RegClientId} = State) -> - roster:info(?MODULE, "~p:~p:Auth/voice", [Phone, RegClientId]), + ?LOG_INFO("~p:~p:Auth/voice", [Phone, RegClientId]), {reply, {bert, #io{ code = case kvs:get('Auth', RegClientId) of {ok, #'Auth'{ @@ -398,7 +394,7 @@ info(#'Auth'{ } = Stored} -> case verify(Services, Code) of true -> - roster:info(?MODULE, "~p:~p:Auth/verify~p:~p", [Phone, RegClientId, Services, DevKey]), + ?LOG_INFO("~p:~p:Auth/verify~p:~p", [Phone, RegClientId, Services, DevKey]), {roster, RosterId} = case kvs:get('Profile', Phone) of {ok, #'Profile'{ @@ -423,7 +419,7 @@ info(#'Auth'{ } || #'Feature'{key = Key} = F <- Settings ], - kvs:put(P = #'Profile'{ + kvs:put( #'Profile'{ phone = Phone, rosters = [Roster], settings = NewSettings, @@ -435,7 +431,7 @@ info(#'Auth'{ UserId = roster:phone_id(Phone, RosterId), ClientId = client(RegClientId), [ - kvs:delete('Auth', ClId) + kvs:delete('Auth', ClId) || #'Auth'{client_id = ClId} <- kvs:index('Auth', dev_key, DevKey) ], {'Token', Token} = roster:gen_token([], []), @@ -490,7 +486,7 @@ info(#'Auth'{ }, Req, #cx{params = <<"emqttd_", _/binary>> = ClientId} = State) -> - roster:info(?MODULE, "~p:Auth/push.Request:~p:~p", [ClientId, OS, Push]), + ?LOG_INFO("~p:Auth/push.Request:~p:~p", [ClientId, OS, Push]), R = case kvs:get('Auth', ClientId) of {ok, Auth2} -> %% check for push token uniqueness @@ -504,17 +500,17 @@ info(#'Auth'{ {error, _} -> #error{code = mismatch_user_data} end, - roster:info(?MODULE, "~p:Auth/push.Response:~p", [ClientId, R]), + ?LOG_INFO("~p:Auth/push.Response:~p", [ClientId, R]), {reply, {bert, #io{code = R}}, Req, State}; info(#'Auth'{type = clear}, Req, #cx{params = <<"emqttd_", _/binary>> = ClientId} = State) -> - roster:info(?MODULE, "~p:Auth/clear", [ClientId]), + ?LOG_INFO("~p:Auth/clear", [ClientId]), IO = #io{ code = case kvs:get('Auth', ClientId) of {ok, #'Auth'{user_id = PhoneId}} -> - [roster:force_logout(A) + [roster:force_logout(A) || #'Auth'{client_id = ClId} = A <- kvs:index('Auth', user_id, PhoneId), ClId /= ClientId ], #ok{code = cleared}; @@ -539,7 +535,7 @@ info(#'Auth'{ } = Auth, Req, #cx{params = <<"emqttd_", _/binary>> = ClientId} = State) -> - roster:info(?MODULE, "~p:Auth/delete", [ClientId]), + ?LOG_INFO("~p:Auth/delete", [ClientId]), IO = case kvs:get('Auth', ClientId) of {ok, #'Auth'{user_id = PhoneId}} -> case [roster:force_logout(A) || #'Auth'{client_id = ClId} = A <- kvs:index('Auth', user_id, PhoneId), ClId == ClientId2] of @@ -556,11 +552,11 @@ info(#'Auth'{ info(#'Auth'{type = get}, Req, #cx{params = <<"emqttd_", _/binary>> = ClientId} = State) -> - roster:info(?MODULE, "~p:Auth/get", [ClientId]), + ?LOG_INFO("~p:Auth/get", [ClientId]), IO = case kvs:get('Auth', ClientId) of {ok, #'Auth'{user_id = PhoneId}} -> [ - Auth#'Auth'{token = []} + Auth#'Auth'{token = []} || #'Auth'{type = Type} = Auth <- kvs:index('Auth', user_id, PhoneId), Type == verified orelse Type == []]; {error, _} = Err -> @@ -571,17 +567,17 @@ info(#'Auth'{type = get}, info(#'Auth'{type = logout}, Req, #cx{params = ClientId} = State) -> - roster:info(?MODULE, "~p:Auth/logout", [ClientId]), + ?LOG_INFO("~p:Auth/logout", [ClientId]), D = case ClientId of <<"emqttd_", _/binary>> -> case kvs:get('Auth', ClientId) of {ok, Auth} -> kvs:put(Auth#'Auth'{type = logout}); _ -> - skip + skip end, {bert, #io{code = #ok{code = logout}}}; - _ -> + _ -> <<>> end, {reply, D, Req, State}; @@ -589,7 +585,7 @@ info(#'Auth'{type = logout}, info(#'Auth'{phone = Phone} = Auth, Req, #cx{params = ClientId} = State) -> - roster:info(?MODULE, "~p:~p:Auth/unknown:~p", [Phone, ClientId, Auth]), + ?LOG_INFO("~p:~p:Auth/unknown:~p", [Phone, ClientId, Auth]), {reply, {bert, #io{code = #error{code = invalid_data}}}, Req, State}. %% roster_auth handlers @@ -604,9 +600,9 @@ proc(init, #handler{name = roster_auth} = Async) -> {ok, _} -> ok; Err -> - roster:error(?MODULE, "error:~p:locus", [Err]) + ?LOG_ERROR("error:~p:locus", [Err]) end, - roster:info(?MODULE, "ASYNC AUTH PROC:~p started", [C]), + ?LOG_INFO("ASYNC AUTH PROC:~p started", [C]), {ok, Async#handler{state = C, seq = 0}}; %%proc(#'Auth'{type = verified, client_id = <<"emqttd_", Post/binary>> = ClientId, user_id = UserId} = Stored, #handler{} = H) -> @@ -621,7 +617,7 @@ proc(#'Auth'{ RegClientId = <<"reg_", Post/binary>>, n2o_vnode:unsubscribe(RegClientId, roster:action_topic(RegClientId)), kvs:delete(mqtt_session, RegClientId), - catch roster_presence:on_verify(ClientId, UserId), + catch roster:sub_client(subscribe, ClientId, UserId), Reply; %%proc(#'Auth'{type = [], token = Token, phone = Phone, client_id = <<"emqttd_", _/binary>> = ClientId}=Auth,H)-> %% proc(Auth#'Auth'{type = {ver,?VERSION}}, H); @@ -633,7 +629,7 @@ proc(#'Auth'{ }, #handler{state = C} = H) -> NewC = roster:restart_module(C, roster_auth), - roster:info(?MODULE, "~p:~p:Auth/login:~p", [Phone, ClientId, binary:part(Token, 0, 16)]), + ?LOG_INFO("~p:~p:Auth/login:~p", [Phone, ClientId, binary:part(Token, 0, 16)]), catch roster_presence:on_connect(Phone, ClientId, NewC, Ver), {reply, [], H#handler{state = NewC}}; proc(#'Auth'{ @@ -644,7 +640,7 @@ proc(#'Auth'{ {ok, #'Auth'{token = T}} = kvs:get('Auth', ClientId), %NewC=roster:restart_module(C,roster_auth), {'Token', Token2} = roster:gen_token([], binary:part(T, 0, 10)), - roster:info(?MODULE, "~p:Auth/update:~p", [ClientId, binary:part(Token2, 0, 16)]), + ?LOG_INFO("~p:Auth/update:~p", [ClientId, binary:part(Token2, 0, 16)]), catch n2o_vnode:send( C, roster:action_topic(ClientId), @@ -665,10 +661,10 @@ proc({vox, #'Profile'{phone = Phone, services = Services} = Profile, Cli}, ok -> {VoxResponse1} = VoxResponse, [Id, U, P] = [ - proplists:get_value(Key, VoxResponse1) + proplists:get_value(Key, VoxResponse1) || Key <- [user_id, user_name, user_password] ], - roster:info(?MODULE, "~p:~p:vox:~p:~p:~p", [Phone, Cli, Id, U, P]), + ?LOG_INFO("~p:~p:vox:~p:~p:~p", [Phone, Cli, Id, U, P]), #'Service'{ type = vox, id = Id, @@ -677,7 +673,7 @@ proc({vox, #'Profile'{phone = Phone, services = Services} = Profile, Cli}, status = added }; _ER -> - [] + [] end, kvs:put(Profile2 = #'Profile'{rosters = Rosters} = Profile#'Profile'{services = lists:flatten([Service | Services])}), case emqttd_cm:lookup(Cli) of @@ -695,7 +691,7 @@ proc({vox, #'Profile'{phone = Phone, services = Services} = Profile, Cli}, end, {reply, [], H}; proc({sms, Phone, SmsCode}, #handler{} = H) -> - roster:info(?MODULE, "~p:sms:~p", [Phone, SmsCode]), + ?LOG_INFO("~p:sms:~p", [Phone, SmsCode]), Half = size(SmsCode) - round(size(SmsCode) / 2), [Codes1, Codes2] = [binary:part(SmsCode, S, Half) || S <- [0, Half]], ClientSms = <<"Welcome to Nynja! Your verification code: ", Codes1/binary, " ", Codes2/binary>>, @@ -709,20 +705,20 @@ proc({sms, Phone, SmsCode}, #handler{} = H) -> {reply, [], H}; proc({voice, Phone, SmsCode, Lang}, #handler{} = H) -> - roster:info(?MODULE, "~p:telesign:~p:~p", [Phone, SmsCode, Lang]), + ?LOG_INFO("~p:telesign:~p:~p", [Phone, SmsCode, Lang]), % telesign_api:telesign_voice_call(Phone, SmsCode, [], Lang), {reply, [], H}; proc({mqttc, C, connected}, - State = #handler{state = C, seq = S}) -> + State = #handler{state = C, seq = S}) -> {ok, State#handler{seq = S + 1}}; -proc({mqttc, _C, disconnected}, State) -> +proc({mqttc, _C, disconnected}, State) -> {ok, State}; proc(Unknown, #handler{} = H) -> - roster:info(?MODULE, "invalid auth data :~p", [Unknown]), + ?LOG_INFO("invalid auth data :~p", [Unknown]), {reply, [], H}. -client(<<"reg_", Post/binary>>) -> +client(<<"reg_", Post/binary>>) -> <<"emqttd_", Post/binary>>; client(ClientId) -> ClientId. @@ -760,12 +756,12 @@ get_geolocal(IP) -> V when is_binary(V) -> {K, V}; _ -> - [] - end + [] + end || {K, Keys} <- L ]); {error, _} -> - [] + [] end. geoval([], Acc) -> Acc; @@ -783,9 +779,9 @@ delete_duplicated_tokens(ClientId, PushToken) -> case SessionId of ClientId -> skip; _ -> - roster:info(?MODULE, "DeleteOldAuthSession:~p:~p", [PushToken, SessionId]), + ?LOG_INFO("DeleteOldAuthSession:~p:~p", [PushToken, SessionId]), kvs:delete('Auth', SessionId) - end + end || #'Auth'{client_id = SessionId} <- kvs:index('Auth', push, PushToken) ]. @@ -807,6 +803,6 @@ control_ver(A, Ver) -> A#'Auth'{type = {ver, Ver}}; verified -> A#'Auth'{type = {reg, Ver}}; - _ -> + _ -> A end. diff --git a/apps/roster/src/protocol/roster_bpe.erl b/apps/roster/src/protocol/roster_bpe.erl index 2e105a5bc3f01114e617b00951706c852e032d70..96b89d4649b5d02ee2ad1fb87630ae56dcea653d 100644 --- a/apps/roster/src/protocol/roster_bpe.erl +++ b/apps/roster/src/protocol/roster_bpe.erl @@ -1,4 +1,5 @@ -module(roster_bpe). +-include_lib("kernel/include/logger.hrl"). -include_lib("roster.hrl"). %%-include_lib("kvs/include/feed.hrl"). %%-include_lib("kvs/include/kvs.hrl"). @@ -16,7 +17,7 @@ start() -> n2o_async:start(#handler{module = ?MODULE, class = system, group = ro info(#'History'{roster_id = Roster, feed = Feed, size = N, entity_id = MId, status = get} = Data, Req, #cx{params = ClientId} = State) -> - roster:info(?MODULE, "~p:~p:History/get:~p", [Roster, ClientId, Feed]), + ?LOG_INFO("~p:~p:History/get:~p", [Roster, ClientId, Feed]), PhoneId = roster:phone_id(ClientId), %FId = roster:roster_id(Roster), %N0=case N of Size when N < -8 -> -7; _ when MId=:=0 -> -7; Size when N > 8 -> 7; Size -> Size end, @@ -25,37 +26,40 @@ info(#'History'{roster_id = Roster, feed = Feed, size = N, entity_id = MId, stat case kvs:get(writer, Feed) of {ok, #writer{first = #'Job'{context = RID}} = W} -> {RID, W}; _ -> {0, 0} end; - _ -> roster:info(?MODULE, "~p:Feed/error:~p", [Roster, Feed]), {0, -1} + _ -> ?LOG_INFO("~p:Feed/error:~p", [Roster, Feed]), {0, -1} end, D = case kvs:get(reader, R) of {ok, #reader{cache = []} = Rdr} when MId == 0 -> - #writer{count = Count, cache = #'Job'{id = Id}} = Writer, + #writer{count = Count, cache = #'Job'{}} = Writer, case Count of - 0 -> n2o:error(?MODULE, "There are not messages: ~p:~n", [Writer]), {[], 0}, <<>>; - _ -> #reader{cache = {_, RId}} = kvs_stream:save(kvs_stream:top((Rdr#reader{dir = 0}))), + 0 -> + ?LOG_ERROR("There are not messages: ~p:~n", [Writer]), + <<>>; + _ -> + #reader{cache = {_, RId}} = kvs_stream:save(kvs_stream:top((Rdr#reader{dir = 0}))), %{ok,#'Job'{prev = ID }}=kvs:get('Job',RId), Js = roster:exclude({'Job', RId}, #'Job'.status, -?JOBS_LIMIT, 0, []), Data#'History'{data = Js} %[ case S of complete-> J#'Job'{data=[]}; _ -> J end || J=#'Job'{status=S}<-Js]} end; - {ok, #reader{pos = Pos} = Reader} when N == [], MId == 0 -> - #writer{count = Top, cache = #'Job'{id = Id}} = Writer, + {ok, #reader{pos =_Pos} =_Reader} when N == [], MId == 0 -> + #writer{count =_Top, cache = #'Job'{id = Id}} = Writer, %% #reader{cache={_,RId} }=kvs_stream:drop(Reader#reader{args = Pos, dir=1}), %% #reader{cache={_,RId} }=RR=kvs_stream:bot(Reader), % {ok,#'Job'{prev = ID }}=kvs:get('Job',RId), Js = roster:exclude({'Job', Id}, #'Job'.status, -?JOBS_LIMIT, 0, []), - NewP = Top, Data#'History'{data = Js}; - {ok, #reader{pos = Pos} = Reader} when N == [], MId =/= 0 -> - #writer{count = Top} = Writer, + Data#'History'{data = Js}; + {ok, #reader{}} when N == [], MId =/= 0 -> + #writer{} = Writer, %{ok,#'Job'{prev = ID }}=kvs:get('Job',MId), Js = roster:exclude({'Job', MId}, #'Job'.status, ?JOBS_LIMIT, 0, []), NewP = length(Js), Data#'History'{data = Js, size = NewP}; - {ok, #reader{pos = Pos}} when N < 0, MId =/= 0 -> + {ok, #reader{}} when N < 0, MId =/= 0 -> {ok, #'Job'{next = ID}} = kvs:get('Job', MId), Js = roster:exclude({'Job', ID}, #'Job'.status, N, 0, []), NewP = length(Js), Data#'History'{data = Js, size = NewP}; - {ok, #reader{pos = Pos}} when MId =/= 0 -> + {ok, #reader{}} when MId =/= 0 -> {ok, #'Job'{prev = ID}} = kvs:get('Job', MId), Js = roster:exclude({'Job', ID}, #'Job'.status, N, 0, []), NewP = length(Js), @@ -67,8 +71,8 @@ info(#'History'{roster_id = Roster, feed = Feed, size = N, entity_id = MId, stat {reply, {bert, D}, Req, State}; info(#'Job'{id = [], feed_id = {act, <<"publish">>, PhoneId} = Feed, time = [], data = [#'Message'{} | _] = Msgs, status = init} = RequestData, R, - #cx{params = ClientId, client_pid = C, session = Token} = S) -> - roster:info(?MODULE, "Job/init:~p", [Feed]), + #cx{params = ClientId} = S) -> + ?LOG_INFO("Job/init:~p", [Feed]), %J = J0#'Job'{id = Id = kvs:next_id('Job', 1)}, Replay = case roster:phone_id(ClientId) of PhoneId -> %roster:restart_module(Pid,?MODULE), @@ -79,8 +83,8 @@ info(#'Job'{id = [], feed_id = {act, <<"publish">>, PhoneId} = Feed, time = [], {reply, {bert, Replay}, R, S}; info(#'Job'{id = [], feed_id = {act, <<"roster">>,PhoneId} = Feed, time = [], data = Actions, status = init} = RequestData, R, - #cx{params = ClientId, client_pid = C, session = Token} = S) -> - roster:info(?MODULE, "Job/init:~p", [Feed]), + #cx{params = ClientId} = S) -> + ?LOG_INFO("Job/init:~p", [Feed]), %J = J0#'Job'{id = Id = kvs:next_id('Job', 1)}, Replay = case roster:phone_id(ClientId) of PhoneId ->[ action(A, R, S) || A <-Actions]; @@ -91,10 +95,10 @@ info(#'Job'{id = [], feed_id = {act, <<"roster">>,PhoneId} = Feed, time = [], da info(#'Job'{id = [], feed_id = {act, <<"publish">>, PhoneId} = Feed, time = Time, data = [#'Message'{} | _] = D0, status = init} = J0, R, - #cx{params = ClientId, client_pid = C} = S) -> + #cx{params = ClientId} = S) -> %J = J0#'Job'{id = Id = kvs:next_id('Job', 1)}, Current = roster:now_msec(), - roster:info(?MODULE, "Create Job :~p :Time ~p", [Feed, roster:msToUT(Time)]), + ?LOG_INFO("Create Job :~p :Time ~p", [Feed, roster:msToUT(Time)]), % Size=length(D0), %{D,T}=lists:split( case Size > ?JOBS_MS_LIMIT of true -> ?JOBS_MS_LIMIT; false -> Size end, D0), D = D0, @@ -115,9 +119,9 @@ info(#'Job'{id = [], feed_id = {act, <<"publish">>, PhoneId} = Feed, time = Time end, {reply, {bert, Replay}, R, S}; -info(#'Job'{id = Id, time = T0, feed_id = {act, <<"publish">>, PhoneId} = Feed, data = D, settings = Stt, status = update} = RequestData, R, +info(#'Job'{id = Id, time = T0, feed_id = {act, <<"publish">>, PhoneId}, data = D, settings = Stt, status = update} = RequestData, R, #cx{params = ClientId, client_pid = C} = S) -> - roster:info(?MODULE, "UPDATE Time:~p", [roster:msToUT(T0)]), + ?LOG_INFO("UPDATE Time:~p", [roster:msToUT(T0)]), Current = roster:now_msec(), Replay = case roster:phone_id(ClientId) of PhoneId when T0 > Current -> @@ -150,13 +154,13 @@ info(#'Job'{id = Id, time = T0, feed_id = {act, <<"publish">>, PhoneId} = Feed, {reply, {bert, Replay}, R, S}; -info(#'Job'{id = Id, proc = Proc, data = <<"stop">>, status = update} = M, R, S) -> - roster:info(?MODULE, "STOP:~w", [M]), +info(#'Job'{proc = Proc, data = <<"stop">>, status = update} = M, R, S) -> + ?LOG_INFO("STOP:~w", [M]), Docs = #'act'{data = <<"stop">>}, {reply, {bert, {io, bpe:amend(Proc, Docs, noflow), <<>>}}, R, S}; info(#'Job'{proc = Proc, data = D, time = Time, events = [], status = restart} = J0, R, S) -> - roster:info(?MODULE, "UPDATE :~w", [J0]), + ?LOG_INFO("UPDATE :~w", [J0]), P = bpe:load(Proc), % bpe:find_pid(Proc) ! {'DOWN', <<>>, <<>>, <<>>, <<>>}, T = calendar:time_difference(erlang:universaltime(), roster:msToUT(Time)), @@ -166,10 +170,10 @@ info(#'Job'{proc = Proc, data = D, time = Time, events = [], status = restart} = info(#'Job'{id = Id, feed_id = {act, <<"publish">>, UID} = Feed, status = delete} = RequestData, R, #cx{params = ClientId, client_pid = C} = S) -> - roster:info(?MODULE, "JOB delete:~w", [Feed]), + ?LOG_INFO("JOB delete:~w", [Feed]), PhoneId = roster:phone_id(ClientId), D = case kvs:get('Job', Id) of - {ok, #'Job'{feed_id = {act, <<"publish">>, PhoneId}, proc = Proc, time = Time} = J} when UID == PhoneId -> + {ok, #'Job'{feed_id = {act, <<"publish">>, PhoneId}} = J} when UID == PhoneId -> Job = J#'Job'{status = delete}, case kvs_stream:load_writer(Feed) of #writer{cache = #'Job'{id = JID}} = W when JID == Id -> @@ -184,18 +188,18 @@ info(#'Job'{id = Id, feed_id = {act, <<"publish">>, UID} = Feed, status = delete {reply, {bert, D}, R, S}; info(#'Job'{feed_id = Feed} = RequestData, R, S) -> - roster:info(?MODULE, "~p::Job/unknown", [Feed]), + ?LOG_INFO("~p::Job/unknown", [Feed]), {reply, {bert, roster_channel_helper:error_response_400(RequestData) %% #io{code = #error{code = invalid_data}} }, R, S}; -info(M, R, S) -> roster:info(?MODULE, "UNKNOWN:~w", [M]), {unknown, M, R, S}. +info(M, R, S) -> ?LOG_INFO("UNKNOWN:~w", [M]), {unknown, M, R, S}. proc(init, #handler{name = roster_bpe} = Async) -> {ok, C} = emqttc:start_link([{client_id, <<"sys_bpe">>}, {logger, {console, error}}, {reconnect, 5}]), Table = process, Proc = case kvs:get(feed, Table) of - {ok, #feed{top = Top} = Feed} -> + {ok, #feed{top = Top} =_Feed} -> %% ProcIDs=kvs:fold(fun(#process{name=Name}=A,Acc) -> %% case Name of 'Shedule' -> [element(2,A)|Acc]; _ -> Acc end end,[], %% Table, Feed#feed.top,undefined, #iterator.prev,#kvs{mod=store_mnesia}), @@ -219,18 +223,18 @@ proc(init, #handler{name = roster_bpe} = Async) -> {ok, Id0} = bpe:start(job:def(#'Schedule'{id = {0, {0, 0, 1}}, proc = <<"publish">>}), []), bpe:complete(Id0), Id0 end, % bpe:amend(Proc,{send_pid, C},noflow), - roster:info(?MODULE, "ASYNC BPE started: ~p; ~p", [C, Proc]), + ?LOG_INFO("ASYNC BPE started: ~p; ~p", [C, Proc]), % Proc is transfered through state {ok, Async#handler{state = {C, Proc}, seq = 0}}; -proc({update, #'Job'{id = Id0, time = Time, feed_id = {act, <<"publish">>, PhoneId} = Feed, data = D} = J, {OldTime, Limit}, - #cx{params = ClientId} = CX}, #handler{state = {C, Proc}} = H) when Limit < 8 -> +proc({update, #'Job'{id = Id0, time = Time, feed_id = {act, <<"publish">>, PhoneId} = Feed} = J, {OldTime, Limit}, + #cx{} = CX}, #handler{state = {C, Proc}} = H) when Limit < 8 -> roster:restart_module(C,roster_bpe), P = bpe:load(Proc), {NewJ0, Id} = case Id0 of [] -> NId = kvs:next_id('Job', 1), {J#'Job'{id = NId, proc = Proc}, NId}; _ -> {J#'Job'{proc = Proc}, Id0} end, {T, NewD, Par} = case bpe:doc(#'Schedule'{}, P) of - #'Schedule'{id = Current, data = Jobs} = Sh when abs(Time - Current) =< 2 * ?QUANT -> + #'Schedule'{id = Current, data = Jobs} =_Sh when abs(Time - Current) =< 2 * ?QUANT -> %bpe:amend(Proc, NewSh = Sh#'Schedule'{data = lists:usort(Jobs++[{'Job', Id}])},noflow), {Current, Jobs ++ [{'Job', Id}], noflow}; #'Schedule'{id = Current, data = Jobs} when Time - Current < -2 * ?QUANT orelse Jobs == <<"stop">> -> @@ -239,7 +243,7 @@ proc({update, #'Job'{id = Id0, time = Time, feed_id = {act, <<"publish">>, Phone %% #'Schedule'{id = Current, data = Jobs} = Sh when Time - Current < -2*?QUANT -> %% %bpe:amend(Proc, NewSh = #'Schedule'{id = Time, proc =Proc, data = [{'Job', Id}]}), %% {Time,[{'Job', Id}],[]}; - #'Schedule'{id = Current, data = Jobs} = Sh when abs(OldTime - Current) =< 2 * ?QUANT -> + #'Schedule'{id = Current, data = Jobs} =_Sh when abs(OldTime - Current) =< 2 * ?QUANT -> %bpe:amend(Proc, NewSh = Sh#'Schedule'{data =Jobs-- [{'Job', Id}]}), {Time, Jobs-- [{'Job', Id}], []}; [] -> @@ -249,10 +253,10 @@ proc({update, #'Job'{id = Id0, time = Time, feed_id = {act, <<"publish">>, Phone {Time, [], []} end, NewJ = NewJ0#'Job'{time = T, proc = Proc}, - roster:info(?MODULE, "Job started: ~p;~p", [Id, Limit]), + ?LOG_INFO("Job started: ~p;~p", [Id, Limit]), case kvs_stream:load_writer(Feed) of #writer{} = W when OldTime == 0 -> - #writer{first = #'Job'{context = R}} = kvs_stream:save(kvs_stream:add(W#writer{args = NewJ})); + #writer{first = #'Job'{}} = kvs_stream:save(kvs_stream:add(W#writer{args = NewJ})); #writer{cache = #'Job'{id = JID}} = W when JID == Id -> kvs_stream:save(W#writer{cache = NewJ}), kvs:put(NewJ); _ -> kvs:put(NewJ) @@ -268,29 +272,29 @@ proc({update, #'Job'{id = Id0, time = Time, feed_id = {act, <<"publish">>, Phone [] -> roster:send_ses(C, roster:phone(PhoneId), case OldTime of 0 -> NewJ; _ -> NewJ#'Job'{status = update} end), {reply, [], H#handler{state = {C, Proc}}}; - _ -> try amend(Proc, NewSh = #'Schedule'{id = T, proc = Proc, data = NewD}, Par), + _ -> try amend(Proc, #'Schedule'{id = T, proc = Proc, data = NewD}, Par), roster:send_ses(C, roster:phone(PhoneId), case OldTime of 0 -> NewJ; _ -> NewJ#'Job'{status = update} end), {reply, [], H#handler{state = {C, Proc}}} - catch Err:Rea -> n2o:error(?MODULE, "Catch:~p~n", [n2o:stack_trace(Err, Rea)]), + catch Err:Rea -> ?LOG_ERROR("Catch:~p~n", [n2o:stack_trace(Err, Rea)]), restart_bpe(P, C), timer:sleep(300), bpe:complete(Proc), - proc({update, NewJ0, {OldTime, Limit + 1}, CX}, #handler{state = {C, Proc}, seq = S} = H) + proc({update, NewJ0, {OldTime, Limit + 1}, CX}, #handler{state = {C, Proc}} = H) end end; -proc({update, #'Job'{id = Id0, time = Time, feed_id = {act, <<"publish">>, PhoneId} = Feed, data = D} = J, {OldTime, Limit}, - #cx{params = ClientId} = S}, #handler{state = {C, Proc}} = H) -> - roster:info(?MODULE, "BPE update limit ~p; ~p", [C, Proc]), +proc({update, #'Job'{feed_id = {act, <<"publish">>,_PhoneId}}, {_OldTime,_Limit}, + #cx{} = _S}, #handler{state = {C, Proc}} = H) -> + ?LOG_INFO("BPE update limit ~p; ~p", [C, Proc]), {reply, [], H}; proc({publish, #process{id = Id} = P}, #handler{state = {C, Proc}, seq = S} = H) -> - roster:info(?MODULE, "BPE PROC publish: ~p", [Id]), + ?LOG_INFO("BPE PROC publish: ~p", [Id]), roster:restart_module(C,roster_bpe), %#act{data=Jobs}=bpe:doc(#act{},Proc), %{ok, #'Schedule'{id=Tsh, data=Jobs}}=kvs:get('Schedule',Current), Curr= case bpe:doc(#'Schedule'{}, P) of - #'Schedule'{id = Current, data = [{'Job', I}|_]=Jobs} -> + #'Schedule'{id = Current, data = [{'Job',_I}|_]=Jobs} -> Msgs = lists:flatten([begin case kvs:get('Job', I) of {ok, #'Job'{feed_id = {act, <<"publish">>, PhoneId} = Feed, status = pending, data = Ms} = J} -> NewJ = J#'Job'{data = [], status = complete}, @@ -302,15 +306,15 @@ proc({publish, #process{id = Id} = P}, #handler{state = {C, Proc}, seq = S} = H) roster:send_ses(C, roster:phone(PhoneId), NewJ), Ms; _ -> [] end end || {'Job', I} <- Jobs]), - [begin roster:send_event(C, <<"sys_bpe">>, <<>>, Msg, roster:get_vnode(<<"sys_bpe">>,Msg)), timer:sleep(1) end || #'Message'{feed_id = Feed}=Msg <- Msgs], + [begin roster:send_event(C, <<"sys_bpe">>, <<>>, Msg, roster:get_vnode(<<"sys_bpe">>,Msg)), timer:sleep(1) end || #'Message'{}=Msg <- Msgs], Current; #'Schedule'{id = Current} -> Current; _ -> roster:now_msec() end, job:clear_hist(P), proc({next, P, Curr}, #handler{state = {C, Proc}, seq = S} = H); -proc({next, #process{id = Id} = P, Current}, #handler{state = {C, Proc}, seq = S} = H) -> - %roster:info(?MODULE, "BPE PROC next: ~p", [P]), +proc({next, #process{id = Id}, Current}, #handler{state = {_C, _Proc}} = H) -> + %?LOG_INFO("BPE PROC next: ~p", [P]), FirstS = try mnesia:dirty_first('Schedule') of '$end_of_table' -> Current; Key -> Key catch _ -> Current end, Stop = #'Schedule'{id = roster:now_msec(), data = <<"stop">>}, Docs = case job:next('Schedule', Current) of @@ -322,31 +326,31 @@ proc({next, #process{id = Id} = P, Current}, #handler{state = {C, Proc}, seq = S _ -> [Stop] end end, - try bpe:amend(Id, Docs) catch Err:Rea -> n2o:error(?MODULE, "Catch:~p~n", [n2o:stack_trace(Err, Rea)]) end, + try bpe:amend(Id, Docs) catch Err:Rea -> ?LOG_ERROR("Catch:~p~n", [n2o:stack_trace(Err, Rea)]) end, kvs:delete('Schedule', Current), {reply, [], H}; -proc({restart, #'process'{id = Id} = P}, #handler{state = {C, Proc}, seq = S} = H) -> - roster:info(?MODULE, "BPE PROC restarted", []), +proc({restart, #'process'{} = P}, #handler{state = {C, Proc}} = H) -> + ?LOG_INFO("BPE PROC restarted", []), % P1=job_process:update_event(P, 'Action', {0, {0, 0, 1}}), Proc = restart_bpe(P, C), bpe:complete(Proc), {reply, [], #handler{state = {C, Proc}} = H}; -proc({timeout, #'Schedule'{id = Current} = Sh, T}, #handler{state = {C, Proc}} = H) -> - roster:info(?MODULE, "BPE PROC Timeout: ~p", [T]), +proc({timeout, #'Schedule'{id =_Current} = Sh, T}, #handler{state = {_C, Proc}} = H) -> + ?LOG_INFO("BPE PROC Timeout: ~p", [T]), %{value,#boundaryEvent{timeout = BT},_}=lists:keytake('*',#boundaryEvent.name,bpe:events(P)), %{ok, TRef} = timer:apply_after(T, bpe, complete, [Proc]), {ok, TRef} = timer:apply_after(T, roster_bpe, publish, [Proc]), try bpe:amend(Proc, Sh#'Schedule'{state = TRef}, noflow) catch Err:Rea -> - n2o:error(?MODULE, "Catch:~p~n", [n2o:stack_trace(Err, Rea)]) end, kvs:put(Sh#'Schedule'{state = TRef}), + ?LOG_ERROR("Catch:~p~n", [n2o:stack_trace(Err, Rea)]) end, kvs:put(Sh#'Schedule'{state = TRef}), %job_process:update_opt(bpe:load(Proc),{timer,TRef}), {reply, [], H}; -proc({clean, #'process'{id = Id} = P}, #handler{state = {C, Proc}} = H) -> - roster:info(?MODULE, "BPE PROC clean", []), +proc({clean, #'process'{id = Id} = P}, #handler{state = {_C,_Proc}} = H) -> + ?LOG_INFO("BPE PROC clean", []), bpe:complete(Id), {value, #boundaryEvent{timeout = BT}, _} = lists:keytake('*', #boundaryEvent.name, bpe:events(P)), %bpe:find_pid(Id) ! {'DOWN', <<>>, <<>>, <<>>, <<>>}, @@ -354,14 +358,14 @@ proc({clean, #'process'{id = Id} = P}, #handler{state = {C, Proc}} = H) -> {reply, [], H}; proc({send, Msgs}, #handler{state = {C, Proc}} = H) -> - roster:info(?MODULE, "BPE PROC send", []), + ?LOG_INFO("BPE PROC send", []), roster:restart_module(C,roster_bpe), % [case kvs:get(mqtt_session, roster:get_vnode(Msg)) of {ok,#mqtt_session{sess_pid = Pid}} -> n2o_async:send(Pid,{publish, , Msg}); _ -> skip end || Msg = #'Message'{} <- Msgs], [begin roster:send_event(C, <<"sys_bpe">>, <<>>, Msg, roster:get_vnode(<<"sys_bpe">>,Msg)),timer:sleep(5) end || Msg = #'Message'{} <- Msgs], {reply, [], H#handler{state = {C, Proc}}}; -proc({mqttc, C, connected}, State = #handler{state = {C, Proc}, seq = S}) -> {ok, State#handler{seq = S + 1}}; +proc({mqttc, C, connected}, State = #handler{state = {C,_Proc}, seq = S}) -> {ok, State#handler{seq = S + 1}}; proc({mqttc, _C, disconnected}, State) -> {ok, State}. @@ -379,7 +383,7 @@ restart_bpe(#process{id = Id} = P, _C) -> _ -> {ok, Pi} = bpe:start(P, []), Pi end catch - Err:Rea -> {ok, ID} = bpe:start(P, []), ID + _Err:_Rea -> {ok, ID} = bpe:start(P, []), ID end. amend(ProcId, Form, []) -> bpe:amend(ProcId, Form); @@ -399,4 +403,4 @@ set_schedule(_, Time, #'Job'{id = Id, proc = Proc, time = T0} = J) -> action(#'History'{status=update}=Term,R,S)-> - roster_history:info(Term, R, S). \ No newline at end of file + roster_history:info(Term, R, S). diff --git a/apps/roster/src/protocol/roster_favorite.erl b/apps/roster/src/protocol/roster_favorite.erl index 603310f1077e82e2769fe1a7132e2743af3c514e..cc47393d7c367e37e88e061a0ab331bbc4a46965 100644 --- a/apps/roster/src/protocol/roster_favorite.erl +++ b/apps/roster/src/protocol/roster_favorite.erl @@ -1,23 +1,24 @@ -module(roster_favorite). +-include_lib("kernel/include/logger.hrl"). -include("roster.hrl"). -include_lib("n2o/include/n2o.hrl"). -compile(export_all). info(#'Tag'{status = create} = Tag, Req, #cx{params = ClientId} = State) -> - roster:info(?MODULE, "~p:Tag/create:~p", [ClientId, Tag]), + ?LOG_INFO("~p:Tag/create:~p", [ClientId, Tag]), {reply, {bert, Tag}, Req, State}; info(#'Tag'{status = remove} = Tag, Req, #cx{params = ClientId} = State) -> - roster:info(?MODULE, "~p:Tag/remove:~p", [ClientId, Tag]), + ?LOG_INFO("~p:Tag/remove:~p", [ClientId, Tag]), {reply, {bert, Tag}, Req, State}; info(#'Tag'{status = edit} = Tag, Req, #cx{params = ClientId} = State) -> - roster:info(?MODULE, "~p:Tag/edit:~p", [ClientId, Tag]), + ?LOG_INFO("~p:Tag/edit:~p", [ClientId, Tag]), {reply, {bert, Tag}, Req, State}; info(#'ExtendedStar'{}, Req, #cx{params = ClientId, client_pid = C} = State) -> RosterId = roster:roster_id(PhoneId = roster:phone_id(ClientId)), - roster:info(?MODULE, "~p:ExtendedStar/get:~p", [ClientId, PhoneId]), + ?LOG_INFO("~p:ExtendedStar/get:~p", [ClientId, PhoneId]), IO = case kvs:get('Roster', RosterId) of #error{} = Err -> #io{code = Err}; {ok, #'Roster'{favorite = Favs} = Roster} -> @@ -28,7 +29,7 @@ info(#'ExtendedStar'{}, Req, #cx{params = ClientId, client_pid = C} = State) -> {reply, {bert, IO}, Req, State}; info(#'Star'{status = add, message = #'Message'{id = MsgId} = Msg} = Data, Req, #cx{params = ClientId, client_pid = C} = State) -> - roster:info(?MODULE, "~p:Star/add:~p", [ClientId, Data]), + ?LOG_INFO("~p:Star/add:~p", [ClientId, Data]), RosterId = roster:roster_id(PhoneId = roster:phone_id(ClientId)), IO = case kvs:get('Roster', RosterId) of {ok, #'Roster'{favorite = StFavMsgs} = Roster} -> @@ -46,7 +47,7 @@ info(#'Star'{status = add, message = #'Message'{id = MsgId} = Msg} = Data, Req, {reply, {bert, IO}, Req, State}; info(#'Star'{id = MsgId, status = remove} = Data, Req, #cx{params = ClientId, client_pid = C} = State) -> - roster:info(?MODULE, "~p:Star/remove:~p", [ClientId, Data]), + ?LOG_INFO("~p:Star/remove:~p", [ClientId, Data]), Response = case kvs:get('Roster', roster:roster_id(ClientId)) of {ok, #'Roster'{favorite = StFavMsgs} = Roster} -> case lists:keyfind(MsgId, #'Star'.id, StFavMsgs) of @@ -59,13 +60,13 @@ info(#'Star'{id = MsgId, status = remove} = Data, Req, #cx{params = ClientId, cl end; #error{} -> #io{code = #error{code = roster_not_found}} end, - roster:info(?MODULE, "Debug.Response:~p", [Response]), + ?LOG_INFO("Debug.Response:~p", [Response]), {reply, {bert, Response}, Req, State}; info(#'Star'{} = Star, Req, #cx{params = ClientId} = State) -> - roster:info(?MODULE, "~p:Star/unknown:~p", [ClientId, Star]), + ?LOG_INFO("~p:Star/unknown:~p", [ClientId, Star]), {reply, {bert, #io{code = #error{code = invalid_data}}}, Req, State}; info(#'Tag'{} = Tag, Req, #cx{params = ClientId} = State) -> - roster:info(?MODULE, "~p:Tag/unknown:~p", [ClientId, Tag]), + ?LOG_INFO("~p:Tag/unknown:~p", [ClientId, Tag]), {reply, {bert, #io{code = #error{code = invalid_data}}}, Req, State}. diff --git a/apps/roster/src/protocol/roster_friend.erl b/apps/roster/src/protocol/roster_friend.erl index 30ae11bf62866091a774bf6a7a0520d31c46f9c3..d9bd826e6214655c4043728ff7900f178c1a03de 100644 --- a/apps/roster/src/protocol/roster_friend.erl +++ b/apps/roster/src/protocol/roster_friend.erl @@ -1,4 +1,5 @@ -module(roster_friend). +-include_lib("kernel/include/logger.hrl"). -include("roster.hrl"). -include("micro.hrl"). -include_lib("n2o/include/n2o.hrl"). @@ -29,7 +30,7 @@ info(#'Friend'{phone_id = Me, friend_id = Friend, settings = FriendSettings, sta roster:send_ses(C, To, roster:presence(#'Contact'{reader = Rs} = roster:readmsgs(Mec, Friend))), roster:send_ses(C, From, roster:presence(Friendc#'Contact'{reader = lists:reverse(Rs)})), n2o_async:pid(system, ?MODULE) ! {send_push, Me, Friend, <<"request">>}, - roster:info(?MODULE, "~p:~p:Friend/request:~p", [Me, ClientId, Friend]), + ?LOG_INFO("~p:~p:Friend/request:~p", [Me, ClientId, Friend]), <<>> end end, @@ -38,22 +39,22 @@ info(#'Friend'{phone_id = Me, status = request} = F, Req, #cx{params = ClientId} PhoneId = case ClientId of <<"sys_", _/binary>> -> Me; <<"emqttd_", _/binary>> -> roster:phone_id(ClientId); - __________ -> roster:info(?MODULE, "invalid client id: ~p", [ClientId]), error + __________ -> ?LOG_INFO("invalid client id: ~p", [ClientId]), error end, case PhoneId of ?IS_UUID -> - roster:info(?MODULE, "invalid phone id: ~p", [PhoneId]), + ?LOG_INFO("invalid phone id: ~p", [PhoneId]), {reply, {bert, #io{code = {error, corrupted_db}}}, Req, State}; Me -> info(F#'Friend'{phone_id = Me}, {Req, handled}, State); _ -> - roster:info(?MODULE, "invalid phone id: ~p", [Me]), + ?LOG_INFO("invalid phone id: ~p", [Me]), {reply, {bert, #io{code = {error, permission_denied}}}, Req, State} end; info(#'Friend'{friend_id = Friend, status = ignore}, Req, #cx{params = ClientId, client_pid = C} = State) -> Me = roster:phone_id(ClientId), - roster:info(?MODULE, "~p:~p:Friend/ignore:~p", [Me, ClientId, Friend]), + ?LOG_INFO("~p:~p:Friend/ignore:~p", [Me, ClientId, Friend]), IO = case roster:get_contact(Me, Friend) of {error, _} = E -> #io{code = E}; #'Contact'{status = authorization} = Contact -> @@ -66,7 +67,7 @@ info(#'Friend'{friend_id = Friend, status = ignore}, Req, #cx{params = ClientId, info(#'Friend'{friend_id = Friend, phone_id = Sender, status = confirm}, Req, #cx{params = ClientId, client_pid = C} = State) -> Me = case ClientId of <<"sys_", _/binary>> -> Sender; <<"emqttd_", _/binary>> -> roster:phone_id(ClientId) end, - roster:info(?MODULE, "~p:Friend/confirm:~p:~p", [ClientId, Me, Friend]), + ?LOG_INFO("~p:Friend/confirm:~p:~p", [ClientId, Me, Friend]), [{ok, #'Roster'{phone = From} = FromR}, {ok, #'Roster'{phone = To} = ToR}] = [kvs:get('Roster', roster:roster_id(PId)) || PId <- [Me, Friend]], D = case {roster:get_contact(FromR, Friend), roster:get_contact(ToR, Me)} of @@ -87,8 +88,8 @@ info(#'Friend'{friend_id = Friend, phone_id = Sender, status = confirm}, Req, #c Remote = roster:update_contact(ToR, Mec#'Contact'{last_msg = [], created = Now = roster:now_msec()}), Local = roster:update_contact(FromR, Friendc#'Contact'{last_msg = [], created = Now}), TSs = [{ac_topic, subscribe}, {muc_topic, unsubscribe}], - [n2o_vnode:Sub(Client, roster:Topic(Me)) || Client <- Tos, {Topic, Sub} <- TSs], - [n2o_vnode:Sub(Client, roster:Topic(Friend)) || Client <- Froms, {Topic, Sub} <- TSs], + [n2o_vnode:Sub(Client, roster:TopicLocal(Me)) || Client <- Tos, {TopicLocal, Sub} <- TSs], + [n2o_vnode:Sub(Client, roster:TopicLocal(Friend)) || Client <- Froms, {TopicLocal, Sub} <- TSs], [n2o_vnode:subscribe(Client, Topic) || Client <- Tos ++ Froms], roster:send_ses(C, To, Mec2 = roster:presence(#'Contact'{reader = Rs} = roster:readmsgs(Mec#'Contact'{update = Now, created = Now, unread = 1}, Friend))), roster:send_ses(C, From, roster:presence(Friendc#'Contact'{update = Now, created = Now, unread = 1, reader = lists:reverse(Rs)})), @@ -101,10 +102,10 @@ info(#'Friend'{friend_id = Friend, phone_id = Sender, status = confirm}, Req, #c info(#'Friend'{friend_id = Friend, status = Status}, Req, #cx{params = ClientId, client_pid = C} = State) when Status == ban; Status == unban -> Me = roster:phone_id(ClientId), - roster:info(?MODULE, "~p:Friend/~p:~p:~p", [ClientId, Status, Me, Friend]), + ?LOG_INFO("~p:Friend/~p:~p:~p", [ClientId, Status, Me, Friend]), [From, FromId] = roster:parts_phone_id(Me), [To, ToId] = roster:parts_phone_id(Friend), - [Tos, Froms] = [emqttd_pubsub:subscribers(roster:ses_topic(P)) || P <- [To, From]], + [_Tos, Froms] = [emqttd_pubsub:subscribers(roster:ses_topic(P)) || P <- [To, From]], [{ok, FromR}, {ok, ToR}] = [kvs:get('Roster', Id) || Id <- [FromId, ToId]], Fc = #'Contact'{status = FrSt} = roster:get_contact(FromR, Friend), Mc = #'Contact'{status = MeSt} = roster:get_contact(ToR, Me), @@ -131,7 +132,7 @@ info(#'Friend'{friend_id = Friend, status = Status}, Req, [n2o_vnode:Unsub(Client, AcFrT) || Client <- Froms], [n2o_vnode:Unsub(Client, roster:p2p_topic(Me, Friend)) || Client <- Froms], case roster:is_shared_rooms(FromR#'Roster'.roomlist, - [R || #'Room'{status = Status} = R <- ToR#'Roster'.roomlist, Status /= removed]) of + [R || #'Room'{status = StatusLocal} = R <- ToR#'Roster'.roomlist, StatusLocal /= removed]) of true -> [n2o_vnode:Sub(Client, MucFrT) || Client <- Froms]; _ -> skip end, roster:send_ses(C, To, roster:presence(#'Contact'{reader = Rs} = roster:readmsgs(MeC, Friend))), @@ -141,12 +142,12 @@ info(#'Friend'{friend_id = Friend, status = Status}, Req, info(#'Friend'{phone_id = PhoneId, friend_id = Friend, settings = NewSets, status = update}, Req, #cx{client_pid = C, params = ClientId} = State) -> - roster:info(?MODULE, "~p:Friend/update:~p", [ClientId, Friend]), + ?LOG_INFO("~p:Friend/update:~p", [ClientId, Friend]), IO = case roster:phone_id(ClientId) of PhoneId -> case kvs:get('Roster', roster:roster_id(PhoneId)) of {ok, #'Roster'{userlist = Contacts} = Roster} -> case lists:keyfind(Friend, #'Contact'.phone_id, Contacts) of - #'Contact'{reader = RId, settings = Settings, status = friend} = Contact -> + #'Contact'{settings = Settings, status = friend} = Contact -> C2 = Contact#'Contact'{settings = lists:foldl( fun(#'Feature'{id = FId} = F, Acc) -> lists:keystore(FId, #'Feature'.id, Acc, F) end, @@ -164,12 +165,12 @@ info(#'Friend'{phone_id = PhoneId, friend_id = Friend, settings = NewSets, statu {reply, {bert, IO}, Req, State}; -info(#'Friend'{phone_id = Roster, friend_id = Friend} = F, Req, State) -> - roster:info(?MODULE, "~p:~p:Friend/unknown", [Roster, F]), +info(#'Friend'{phone_id = Roster} = F, Req, State) -> + ?LOG_INFO("~p:~p:Friend/unknown", [Roster, F]), {reply, {bert, <<>>}, Req, State}. proc(init, #handler{name = ?MODULE} = Async) -> - roster:info(?MODULE, "ASYNC", []), + ?LOG_INFO("ASYNC", []), {ok, Async}; proc({send_push, #'Contact'{names = FromName} = FromContactPayload, To, PushType}, #handler{} = H) -> diff --git a/apps/roster/src/protocol/roster_ftp.erl b/apps/roster/src/protocol/roster_ftp.erl index 8a498de0901bdf19417aaf59017fd9f4c9aa6d58..5cc449b3736923c22bda2fe610e972daa4733867 100644 --- a/apps/roster/src/protocol/roster_ftp.erl +++ b/apps/roster/src/protocol/roster_ftp.erl @@ -1,4 +1,5 @@ -module(roster_ftp). +-include_lib("kernel/include/logger.hrl"). -include("roster.hrl"). -include_lib("n2o/include/n2o.hrl"). -include_lib("kernel/include/file.hrl"). @@ -10,18 +11,18 @@ % Callbacks -filename(#ftp{sid = Sid, filename = FileName}) -> FileName. %filename:join(nitro:to_list(Sid),FileName). +filename(#ftp{filename = FileName}) -> FileName. %filename:join(nitro:to_list(Sid),FileName). % File Transfer Protocol info(#ftp{status = {event, _}} = FTP, Req, State) -> - roster:info(?MODULE, "Event Message: ~p", [FTP#ftp{data = <<>>}]), + ?LOG_INFO("Event Message: ~p", [FTP#ftp{data = <<>>}]), Module = case State#cx.module of [] -> index; M -> M end, Reply = try Module:event(FTP) catch E:R -> Error = n2o:stack(E, R), - roster:info(?MODULE, "Catch: ~p:~p~n~p", Error), + ?LOG_INFO("Catch: ~p:~p~n~p", Error), Error end, {reply, n2o:format({io, n2o_nitro:render_actions(n2o:actions()), Reply}), Req, State}; @@ -29,12 +30,12 @@ info(#ftp{status = {event, _}} = FTP, Req, State) -> info(#ftp{id = Link, status = <<"init">>, block = Block, offset = Offset} = FTP, Req, State) -> Root = ?ROOT, RelPath = (application:get_env(roster, filename, roster_ftp)):filename(FTP), - roster:info(?MODULE, "RelPath ~p", [RelPath]), + ?LOG_INFO("RelPath ~p", [RelPath]), FilePath = filename:join(Root, RelPath), ok = filelib:ensure_dir(FilePath), FileSize = case file:read_file_info(FilePath) of {ok, Fi} -> Fi#file_info.size; {error, _} -> 0 end, - roster:info(?MODULE, "Info Init: ~p Offset: ~p Block: ~p", [FilePath, FileSize, Block]), + ?LOG_INFO("Info Init: ~p Offset: ~p Block: ~p", [FilePath, FileSize, Block]), Block2 = case Block of 0 -> ?STOP; _ -> ?NEXT end, Offset2 = case FileSize >= Offset of true -> FileSize; false -> 0 end, @@ -46,12 +47,12 @@ info(#ftp{id = Link, status = <<"init">>, block = Block, offset = Offset} = FTP, {reply, {bert, FTP2}, Req, State}; info(#ftp{id = Link, status = <<"send">>} = FTP, Req, State) -> - roster:info(?MODULE, "Info Send: ~p", [FTP#ftp{data = <<>>}]), + ?LOG_INFO("Info Send: ~p", [FTP#ftp{data = <<>>}]), Reply = try gen_server:call(n2o_async:pid({file, Link}), FTP) catch E:R -> skip, - roster:info(?MODULE, "Info Error call the sync: ~p", [{E, R}]), + ?LOG_INFO("Info Error call the sync: ~p", [{E, R}]), FTP#ftp{data = <<>>, block = ?STOP} end, - roster:info(?MODULE, "Send reply ~p", [Reply#ftp{data = <<>>}]), + ?LOG_INFO("Send reply ~p", [Reply#ftp{data = <<>>}]), {reply, {bert, Reply}, Req, State}; info(Message, Req, State) -> {unknown, Message, Req, State}. @@ -59,9 +60,9 @@ info(Message, Req, State) -> {unknown, Message, Req, State}. % n2o Handlers proc(init, #handler{state = #ftp{sid = Sid, meta = ClientId} = FTP} = Async) -> - roster:info(?MODULE, "Proc Init: ~p~n Sid: ~p ClientId: ~p", [FTP#ftp{data = <<>>}, Sid, ClientId]), + ?LOG_INFO("Proc Init: ~p~n Sid: ~p ClientId: ~p", [FTP#ftp{data = <<>>}, Sid, ClientId]), FTP2 = FTP#ftp{data = <<>>, status = {event, init}}, - roster:info(?MODULE, "~n~n~n~n FTP2 ~p ~n~n~n~n", [FTP2]), + ?LOG_INFO("~n~n~n~n FTP2 ~p ~n~n~n~n", [FTP2]), n2o_ring:send({publish, <<"events/1/index/anon/", ClientId/binary, "/", Sid/binary>>, term_to_binary(FTP2)}), {ok, Async}; @@ -71,7 +72,7 @@ proc(#ftp{id = Link, sid = Sid, data = Data, status = <<"send">>, block = Block, {S3_Status, S3_Data} = amazon_api:push_to_s3([], RelPath, Data, []), case S3_Status of ok -> - roster:info(?MODULE, "~n~n Uploaded to S3 file with link ~p ~n~n", [S3_Data]), + ?LOG_INFO("~n~n Uploaded to S3 file with link ~p ~n~n", [S3_Data]), FTP2 = FTP#ftp{data = <<>>, block = ?STOP}, FTP3 = FTP2#ftp{status = {event, stop}, filename = RelPath}, n2o_ring:send({publish, <<"events/1//index/anon/", ClientId/binary, "/", Sid/binary>>, term_to_binary(FTP3)}), @@ -84,14 +85,14 @@ proc(#ftp{id = Link, sid = Sid, data = Data, status = <<"send">>, block = Block, proc(#ftp{data = Data, block = Block} = FTP, #handler{state = #ftp{offset = Offset, filename = RelPath}} = Async) -> FTP2 = FTP#ftp{status = <<"send">>, offset = Offset + Block}, - roster:info(?MODULE, "Proc Process ~p", [FTP2#ftp{data = <<>>}]), + ?LOG_INFO("Proc Process ~p", [FTP2#ftp{data = <<>>}]), {S3_Status, S3_Data} = amazon_api:push_to_s3([], RelPath, Data, []), case S3_Status of ok -> - roster:info(?MODULE, "~n~n Uploaded to S3 file with link ~p ~n~n", [S3_Data]), + ?LOG_INFO("~n~n Uploaded to S3 file with link ~p ~n~n", [S3_Data]), {reply, FTP2#ftp{data = <<>>}, Async#handler{state = FTP2#ftp{filename = RelPath}}}; _ -> {reply, {error, S3_Data / binary}, Async} end; -proc(_, Async) -> {reply, #ftpack{}, Async}. \ No newline at end of file +proc(_, Async) -> {reply, #ftpack{}, Async}. diff --git a/apps/roster/src/protocol/roster_history.erl b/apps/roster/src/protocol/roster_history.erl index a5243b64978eaf4f2bd8ade95cb383f0cf7e9998..2985bd785ba5b02d30bdd4ad4431ddb1b16d4051 100644 --- a/apps/roster/src/protocol/roster_history.erl +++ b/apps/roster/src/protocol/roster_history.erl @@ -1,4 +1,5 @@ -module(roster_history). +-include_lib("kernel/include/logger.hrl"). -include("roster.hrl"). -include_lib("n2o/include/n2o.hrl"). -include_lib("kvs/include/kvs.hrl"). @@ -13,7 +14,7 @@ start() -> n2o_async:start(#handler{module = ?MODULE, class = system, group = ro info(#'History'{roster_id = RosterId, feed = #'StickerPack'{} = Feed, size = N, status = get} = Data, Req, #cx{params = ClientId} = State) -> - roster:info(?MODULE, "~p:~p:History/get:~p", [RosterId, ClientId, Feed]), + ?LOG_INFO("~p:~p:History/get:~p", [RosterId, ClientId, Feed]), Stickers = case kvs:get('Roster', roster:roster_id(RosterId)) of {ok, #'Roster'{}} -> S = kvs:all('StickerPack'), case N of [] -> S; _ -> lists:sublist(S, N) end; %%TODO Temporary all sticker packs @@ -23,13 +24,13 @@ info(#'History'{roster_id = RosterId, feed = #'StickerPack'{} = Feed, size = N, info(#'History'{status = get_reply, entity_id = Id} = History, Req, #cx{params = ClientId} = State) when Id /= [] -> PhoneId = roster:phone_id(ClientId), - roster:info(?MODULE, "~p:~p:History/get_reply:~p", [PhoneId, ClientId, Id]), + ?LOG_INFO("~p:~p:History/get_reply:~p", [PhoneId, ClientId, Id]), Msgs = case kvs:get('Message', Id) of {ok, #'Message'{repliedby = [_|_] = Replyes}} -> [case kvs:get('Message', ReplId) of {ok, Msg} -> Msg; _ -> [] end || ReplId<-Replyes]; _ -> [] end, {reply, {bert, History#'History'{data = lists:flatten(Msgs)}}, Req, State}; -info(#'History'{status = double_get, feed = Feed, size = N, entity_id = MId, data = []} = History, Req, +info(#'History'{status = double_get, size = N, entity_id = MId, data = []} = History, Req, #cx{} = State) when N > 0, MId > 0 -> {reply, {bert, #'History'{data = Msgs1}}, _, _} = info(History#'History'{status = get, size = N}, Req, State), @@ -52,63 +53,94 @@ info(#'History'{status = get, roster_id = Roster0, feed = Feed, size = N, entity when N /= 0 andalso (is_record(Feed, p2p) orelse is_record(Feed, muc)) andalso (MsgData == [] andalso InitialStatus == get orelse MsgData /= []) -> PhoneId = roster:phone_id(ClientId), - roster:info(?MODULE, "~p:~p:History/get:~p:~p", [PhoneId, ClientId, Feed, History]), + ?LOG_INFO("~p:~p:History/get:~p:~p", [PhoneId, ClientId, Feed, History]), {UID, R, LastMsgId, _Unit} = - case PhoneId of Roster0 -> roster:get_feed_data(Feed, PhoneId); - _ -> {error, permission_denied, [],[]} end, + case PhoneId == Roster0 of + true -> roster:get_feed_data(Feed, PhoneId); + _ -> {error, permission_denied, [],[]} + end, IO = case UID of - error -> roster:info(?MODULE, "History/get.Error:~p", [R]), + error -> ?LOG_INFO("History/get.Error:~p", [R]), #io{code = #error{code = R}}; _ -> - {N2, FId} = case {N, MsgData} of - {_, [#'Message'{id = MId}]} when is_integer(MId) -> throw({error, invalid_data}); - {_, [#'Message'{id = FinishId}]} when is_integer(FinishId), FinishId /= MId -> - K = FinishId-MId, - {case is_integer(N) of true -> round(abs(N)*abs(K)/K); _ -> K end, FinishId}; - {[], []} -> #writer{count = Count} = kvs_stream:load_writer(Feed), - {case MId > 0 of true -> Count;_-> -Count end, []}; - _ -> {N, []} end, + try %% Fix indentation later! + {N2, FId} = + case {N, MsgData} of + {_, [#'Message'{id = MId}]} when is_integer(MId) -> + throw({error, invalid_data}); + {_, [#'Message'{id = FinishId}]} when is_integer(FinishId), FinishId /= MId -> + K = FinishId-MId, + {case is_integer(N) of true -> round(abs(N)*abs(K)/K); _ -> K end, FinishId}; + {[], []} -> + #writer{count = Count} = kvs_stream:load_writer(Feed), + {case MId > 0 of true -> Count; _ -> -Count end, []}; + _ -> + {N, []} + end, Iter = case N2 > 0 of true -> #iterator.prev; _ -> #iterator.next end, %% determine the chain direction - Reader=#reader{cache=Cache} = kvs_stream:load_reader(R), - MId2 = case MId of - 0 when N>0 andalso N/=[] andalso Cache/=[] -> {_,MsId}=Cache,MsId; - 0 when N>0 andalso N/=[] -> case kvs:get(writer,Feed) of - {ok, #writer{first=#'Message'{id = MsId}}}-> MsId; - _ -> LastMsgId end; + Reader = #reader{cache = Cache} = kvs_stream:load_reader(R), %% load the reader + %% Set starting point, 0 means do it automatically + MId2 = + case MId of + %% N > 0 means going forward find cached or first MsgId (if any) + 0 when N>0 andalso N/=[] andalso Cache/=[] -> {_, MsId} = Cache, MsId; + 0 when N>0 andalso N/=[] -> + case kvs:get(writer, Feed) of + {ok, #writer{first=#'Message'{id = MsId}}} -> MsId; + _ -> LastMsgId %% Last resort, there are no messages in this Feed?! + end; 0 -> LastMsgId; - _ -> MId end, - MaxReadId = case MId2 > LastMsgId of true -> LastMsgId; _ -> MId2 end, - Filter = case N of [] -> msg_update; _ -> msg_filter end, %% select filtration function - {InnerFilterFun, Mime2} = case MsgData of - [#'Message'{files = [#'Desc'{mime = Mime = <<_:8, _/binary>>}]}] -> - {fun(#'Message'{files = Descs} = Msg, UID) -> %% filter by mime type - case roster:msg_filter(Msg, UID) of - 1 -> roster:bool_to_int(lists:keyfind(Mime, #'Desc'.mime, Descs)); - 0 -> 0 end end, Mime}; - [] -> {fun roster:Filter/2, []}; - _ -> {fun roster:msg_filter/2, []} - end, + _ -> MId + end, + %% Normalize if MId was too large... + MaxReadId = min(MId2, LastMsgId), + + %% Check that MaxReadId is not before history limit + case roster:message_within_history_limit(MaxReadId, Reader) of + true -> ok; + false -> throw({error, invalid_data}) + end, - StartId = case Reader of - #reader{cache = {'Message', ReadMsg}} - when N2 < 0, MaxReadId < ReadMsg -> ReadMsg; %% if from message is lower then reader is placed - _ -> MaxReadId end, + Filter = case N of [] -> msg_update; _ -> msg_filter end, %% select filter function + {InnerFilterFun, Mime2} = + case MsgData of + [#'Message'{files = [#'Desc'{mime = Mime = <<_:8, _/binary>>}]}] -> + {fun(#'Message'{files = Descs} = Msg, UIDLocal) -> %% filter by mime type + case roster:msg_filter(Msg, UIDLocal) of + 1 -> roster:bool_to_int(lists:keyfind(Mime, #'Desc'.mime, Descs)); + 0 -> 0 + end end, Mime}; + [] -> {fun roster:Filter/2, []}; + _ -> {fun roster:msg_filter/2, []} + end, + + StartId = + case Reader of + #reader{cache = {'Message', ReadMsg}} when N2 < 0, MaxReadId < ReadMsg -> + ReadMsg; %% if from message is lower then reader is placed + _ -> + MaxReadId + end, - AccFun = fun(_, #'Message'{id = Id}, Acc, Dir) when Dir < 0, Id > MaxReadId -> Acc; %% if from message is lower then reader - (1, Msg, Acc, Dir) when Dir < 0 -> Acc++[roster:wrap_msg(Msg)]; - (0, Msg, Acc, Dir) -> Acc; - (1, Msg, Acc, Dir) -> [roster:wrap_msg(Msg)|Acc] end, + AccFun = fun (_, #'Message'{id = Id}, Acc, Dir) when Dir < 0, Id > MaxReadId -> Acc; %% if from message is lower then reader + (1, Msg, Acc, Dir) when Dir < 0 -> Acc ++ [roster:wrap_msg(Msg)]; + (0, _Msg, Acc, _Dir) -> Acc; + (1, Msg, Acc, _Dir) -> [roster:wrap_msg(Msg) | Acc] + end, + + FilterFun = + fun (Msg, {Acc, _} = A) when length(Acc) < abs(N2) -> + (roster:msg_filter_fun2(N2, InnerFilterFun, UID, AccFun))(Msg, A); + (_, Acc) -> Acc + end, - FilterFun = - fun(Msg, {Acc, _} = A) when length(Acc) < abs(N2) -> - (roster:msg_filter_fun(N2, InnerFilterFun, UID, AccFun))(Msg, A); - (_, Acc) -> Acc end, StopFun = fun(Msg, {Acc, _} = A) when length(Acc) < abs(N2) -> (roster:msg_stop_fun([], UID))(Msg, A); (_, Acc) -> Acc end, - {Msgs, _} = roster:fold(FilterFun, {[], roster:start_reader(Reader)}, - 'Message', StartId, FId, #kvs{mod = store_mnesia}, Iter, StopFun), + {Msgs, _} = + roster:fold(FilterFun, {[], roster:start_reader(Reader)}, + 'Message', StartId, FId, #kvs{mod = store_mnesia}, Iter, StopFun), Msgs2 = case is_integer(MId) of @@ -118,7 +150,7 @@ info(#'History'{status = get, roster_id = Roster0, feed = Feed, size = N, entity lists:reverse(lists:ukeymerge(#'Message'.id, lists:reverse(Msgs), [EntityMsg#'Message'{files = []}])); {error, _} -> - roster:info(?MODULE, "message ~p not found", [MId]), + ?LOG_INFO("message ~p not found", [MId]), #error{code = invalid_data} end; _ -> Msgs @@ -133,6 +165,9 @@ info(#'History'{status = get, roster_id = Roster0, feed = Feed, size = N, entity %% CheckedResponseMsgs = [roster:check_message(Msg) || Msg <- ResponseMsgs], case Msgs2 of #error{} = E -> #io{code = E}; _ -> History#'History'{data = Msgs2, size = length(Msgs2), status = InitialStatus} end + catch throw:#error{} = Err -> + #io{code = Err} + end end, {reply, {bert, IO}, Req, State}; @@ -140,7 +175,7 @@ info(#'History'{status = update, feed = Feed, entity_id = MId}, Req, #cx{client_pid = C, params = ClientId, state = verified} = State) -> PhoneId = roster:phone_id(ClientId), From = roster:phone(PhoneId), - roster:info(?MODULE, "~p:History/update:~p,~p", [From, Feed, MId]), + ?LOG_INFO("~p:History/update:~p,~p", [From, Feed, MId]), {UID, R, LastMsgId, Unit} = roster:get_feed_data(Feed, PhoneId), MId2 = case MId > LastMsgId of true -> LastMsgId; _ -> MId end, {Unread, UpdReader} = @@ -148,7 +183,7 @@ info(#'History'{status = update, feed = Feed, entity_id = MId}, Req, {ok, #reader{cache = []} = Rdr0} -> W = #writer{count = Count, first = #'Message'{}} = kvs_stream:load_writer(Feed), case Count of - 0 -> roster:info(?MODULE, "ThereAreNotMessages:~p", [W]), <<>>; + 0 -> ?LOG_INFO("ThereAreNotMessages:~p", [W]), <<>>; _ -> Rdr = kvs_stream:bot((Rdr0#reader{dir = 0})), Reader = kvs_stream:save(roster:mover(Rdr, {'Message', MId2})), {roster:nentries(kvs:get('Message', MId2), Count + 1, UID) - roster:nentries(kvs:get('Message', MId2), 0, UID), Reader} @@ -159,9 +194,9 @@ info(#'History'{status = update, feed = Feed, entity_id = MId}, Req, {ok, #reader{pos = 0, cache = {_, RId}} = Rdr} when MId2 >= RId -> Reader = kvs_stream:save((roster:mover(Rdr#reader{dir = roster:sign(MId2 - RId)}, {'Message', MId2}))#reader{dir = 0}), {roster:nentries2(kvs:get('Message', LastMsgId), MId2, UID), Reader}; - {ok, #reader{pos = Pos, cache = {_, RId}} = Rdr} when MId2 == RId -> + {ok, #reader{pos =_Pos, cache = {_, RId}} =_Rdr} when MId2 == RId -> {<<>>, <<>>}; - {ok, #reader{pos = Pos, cache = {_, RId}} = Rdr} -> + {ok, #reader{cache = {_, RId}} = Rdr} -> % #writer{count = Top} = kvs_stream:load_writer(Feed), Reader = #reader{cache = {_, NRId}} = kvs_stream:save((roster:mover(Rdr#reader{dir = roster:sign(MId2 - RId)}, {'Message', MId2}))#reader{dir = 0}), {roster:nentries2(kvs:get('Message', LastMsgId), NRId, UID), Reader}; @@ -171,7 +206,7 @@ info(#'History'{status = update, feed = Feed, entity_id = MId}, Req, _ when element(1, Unit) == error -> #io{code = Unit}; true -> - {ReadMsg, UpdRoom} = roster:put_readers(read_top, Unit, UpdReader), + {ReadMsg, UpdRoom} = roster:put_readers(Unit, UpdReader), roster:send_feed(C, Feed, ReadMsg), {Record, Readers} = case Feed of @@ -200,7 +235,7 @@ info(#'History'{status = update, feed = Feed, entity_id = MId} = History, Req, # info(#'History'{feed = Feed, status = delete}, Req, #cx{client_pid = C, params = ClientId} = State) -> PhoneId = roster:phone_id(ClientId), - roster:info(?MODULE, "~p:History/delete:~p", [PhoneId, Feed]), + ?LOG_INFO("~p:History/delete:~p", [PhoneId, Feed]), IO = case clean_history(Feed, PhoneId) of #error{} = Res -> #io{code = Res}; {_Unread, #'Message'{}=Internal} -> @@ -212,7 +247,7 @@ info(#'History'{feed = Feed, status = delete}, Req, #cx{client_pid = C, params = {I,_} -> I end, {reply, {bert, IO}, Req, State}; -info(#'History'{roster_id = PhoneId, status = draft}, Req, #cx{client_pid = C, params = ClientId} = State) -> +info(#'History'{roster_id = PhoneId, status = draft}, Req, #cx{params = ClientId} = State) -> PhoneId = roster:phone_id(ClientId), IO = case kvs:get('Roster', roster:roster_id(PhoneId)) of {ok, #'Roster'{userlist = Conts, roomlist = Rooms}} -> @@ -220,57 +255,67 @@ info(#'History'{roster_id = PhoneId, status = draft}, Req, #cx{client_pid = C, p CMs=[M|| #'Contact'{last_msg = M}<-Conts], #'Draft'{data=lists:flatten(CMs++RMs), status = get}; E -> #io{code = E} end, - roster:info(?MODULE, "~p:History/draft:~p", [PhoneId, IO]), + ?LOG_INFO("~p:History/draft:~p", [PhoneId, IO]), {reply, {bert, IO}, Req, State}; info(#'History'{} = Data, Req, State) -> - roster:info(?MODULE, "History/unknown:~p", [Data]), + ?LOG_INFO("History/unknown:~p", [Data]), {reply, {bert, <<>>}, Req, State}. % Helper Section -clean_history(#muc{name = Room} = Feed, <<"emqttd_", _/binary>> = ClientId) -> - case roster:muc_member(PhoneId = roster:phone_id(ClientId), Room) of - #'Member'{status = admin} -> - clean_history(Feed, PhoneId); - _ -> #error{code = permission_denied} - end; clean_history(#muc{name = Room} = Feed, PhoneId) -> - clean_history(Feed, PhoneId, Room, roster:members(Feed)); -clean_history(#p2p{} = Feed, PhoneId) -> - Friend = roster:friend(PhoneId, Feed), - clean_history(Feed, PhoneId, Friend, [roster:get_contact(PhoneId, Friend)]). + case roster:muc_member(PhoneId, Room) of + #'Member'{status = admin} -> clean_history(Feed, PhoneId, Room, roster:members(Feed)); + _ -> #error{code = permission_denied} + end; +clean_history(#p2p{from = From, to = To} = Feed, PhoneId) -> + case PhoneId of + From -> clean_history(Feed, From, To, [roster:get_contact(From, To)]); + To -> clean_history(Feed, To, From, [roster:get_contact(To, From)]); + _ -> #error{code = permission_denied} + end. + clean_history(Feed, From, To, Rs) -> Msg = roster:desc_id(#'Message'{status = clear, feed_id = Feed, from = From, to = To, type = [sys], msg_id = iolist_to_binary([<<"rmv_history_">>, roster:msg_id()]), files = [#'Desc'{payload = <<"History was removed">>}]}), case kvs:get(writer, Feed) of - {ok, #writer{count = Top, id = #muc{name = MUC}} = W} -> - NewW = #writer{cache = #'Message'{next = NewFirst} = M} = + {ok, #writer{id = #muc{name = MUC}} = W} -> + NewW = #writer{cache = #'Message'{id = NewFirst, next = OldLast} = M} = kvs_stream:add(W#writer{args = Msg#'Message'{created = roster:now_msec()}}), - roster:set_seenby(kvs:get('Message', NewFirst), -Top - 1, [-1]), - [kvs_stream:save((kvs_stream:load_reader(R))#reader{pos = 0, cache = {'Message', NewFirst}}) || #'Member'{reader = R} <- Rs, R > 0], - kvs_stream:save(NewW#writer{count = 1, first = M}), {ok, Room} = kvs:get('Room', MUC), - kvs:put(Room#'Room'{readers = []}), - {0, M}; - {ok, #writer{count = Top, id = #p2p{}} = W} -> - NewW = #writer{cache = #'Message'{id = NewFirst, next = NextId} = M} = - kvs_stream:save(kvs_stream:add(W#writer{args = Msg#'Message'{seenby = [To], created = roster:now_msec()}})), [begin - case kvs:get('Message', NextId) of - {ok, NextM} -> roster:set_seenby({ok, NextM}, -Top - 1, [From]); - %% TODO No need roster:set_seenby/4. History funs must stop on "clear" message - _ -> skip end, - kvs_stream:save((kvs_stream:load_reader(R))#reader{pos = 1, cache = {'Message', NewFirst}}) - end || #'Contact'{reader = R} <- Rs], + Reader0 = kvs_stream:load_reader(R), + %% Mark "History removed" as read for the remover and unread for the others + Reader1 = case PhoneId == From of + true -> + %% Set the remover to the last writer of the feed + kvs:put(Room#'Room'{readers = [MemberId]}), + Reader0#reader{pos = 1, cache = {'Message', NewFirst}}; + false -> Reader0#reader{pos = 0, cache = {'Message', OldLast}} + end, + kvs_stream:save(Reader1) + end || #'Member'{id = MemberId, phone_id = PhoneId, reader = R} <- Rs, R > 0], + kvs_stream:save(NewW#writer{count = 1, first = M}), + {0, M}; + {ok, #writer{id = #p2p{}} = Writer} -> + %% The other party should not see the "History removed" message. + Msg1 = Msg#'Message'{seenby = [To || To /= From], created = roster:now_msec()}, + Writer1 = Writer#writer{args = Msg1}, + #writer{cache = #'Message'{id = NewFirst} = M} = kvs_stream:save(kvs_stream:add(Writer1)), + + %% Update From's reader to make the "History removed" message the first in the history. + [#'Contact'{reader = R}] = Rs, + kvs_stream:save((kvs_stream:load_reader(R))#reader{pos = 1, cache = {'Message', NewFirst}}), + {0, M}; _ -> {#io{code = #error{code = invalid_data}}, <<>>} end. proc(init, #handler{name = roster_history} = Async) -> - roster:info(?MODULE, "ASYNC:~p", [?MODULE]), + ?LOG_INFO("ASYNC:~p", [?MODULE]), {ok, Async}; proc({send_push, ToPhoneId, Feed, Action}, #handler{} = H) -> @@ -280,7 +325,7 @@ proc({send_push, ToPhoneId, Feed, Action}, #handler{} = H) -> try Room = #'Room'{} = roster:room(roster:roster_id(ToPhoneId), RoomId), case Action of ?HISTORY_UPDATE_ACTION -> Room#'Room'{last_msg = []}; _ -> Room end catch Err:Rea -> - roster:error(?MODULE, ":~p~n", [n2o:stack_trace(Err, Rea)]) + ?LOG_ERROR(":~p~n", [n2o:stack_trace(Err, Rea)]) end; #p2p{} -> Contact = roster:user(roster:roster_id(ToPhoneId), roster:friend(ToPhoneId, Feed)), @@ -289,4 +334,4 @@ proc({send_push, ToPhoneId, Feed, Action}, #handler{} = H) -> end, PushAlert = iolist_to_binary(["SyncPush:", Action]), [roster_push:send_push_notification(Ses, Payload, PushAlert, PushType) || #'Auth'{} = Ses <- kvs:index('Auth', user_id, ToPhoneId)], - {reply, [], H}. \ No newline at end of file + {reply, [], H}. diff --git a/apps/roster/src/protocol/roster_link.erl b/apps/roster/src/protocol/roster_link.erl index d358145bb54c8b967839f2d522f608b9d12536e9..4cb2ed86746d51acfa34d19c74f953245fc5d10b 100644 --- a/apps/roster/src/protocol/roster_link.erl +++ b/apps/roster/src/protocol/roster_link.erl @@ -1,4 +1,5 @@ -module(roster_link). +-include_lib("kernel/include/logger.hrl"). -include("../../include/roster.hrl"). -include_lib("roster/include/static/roster_text.hrl"). -include_lib("roster/include/static/roster_var.hrl"). @@ -11,8 +12,8 @@ % API % ====================================== info(#'Link'{id = LinkId, type = group, status = join = LStatus} = RequestData, - Req, #cx{params = ClientId, client_pid = C} = State) when LinkId /= [] -> - roster:info(?MODULE, "~p:Link/~p:~p", [ClientId, LStatus, RequestData]), + Req, #cx{params = ClientId} = State) when LinkId /= [] -> + ?LOG_INFO("~p:Link/~p:~p", [ClientId, LStatus, RequestData]), LD=kvs:get('Link',LinkId), Res = case get_entity(LD) of {ok, #'Room'{id = RoomId, last_msg = MsgId} = Room} -> @@ -28,12 +29,12 @@ info(#'Link'{id = LinkId, type = group, status = join = LStatus} = RequestData, NewRoom#'Room'{members = [roster:muc_member(PhoneId,RoomId) | Members], links=[Link], admins = Admins, last_msg = LastMsg, status=joined }; {error, not_found} = Err -> #io{code = Err} end, - roster:info(?MODULE, "Link/~p.Response:~p", [Res, LStatus]), + ?LOG_INFO("Link/~p.Response:~p", [Res, LStatus]), {reply, {bert, Res}, Req, State}; info(#'Link'{id = LinkId, status = get = LStatus} = RequestData, Req, #cx{params = ClientId} = State) when LinkId /= [] -> - roster:info(?MODULE, "~p:Link/~p:~p", [ClientId, LStatus, RequestData]), + ?LOG_INFO("~p:Link/~p:~p", [ClientId, LStatus, RequestData]), LD=kvs:get('Link',LinkId), Res = case get_entity(LD) of {ok, #'Room'{id = RoomId, last_msg = MsgId} = Room} -> @@ -46,14 +47,14 @@ info(#'Link'{id = LinkId, status = get = LStatus} = RequestData, Room#'Room'{members = Members, admins = Admins, links=[Link], last_msg = LastMsg, status = info}; {error, not_found} = Err -> #io{code = Err} end, - roster:info(?MODULE, "Link/~p.Response:~p", [Res, LStatus]), + ?LOG_INFO("Link/~p.Response:~p", [Res, LStatus]), {reply, {bert, Res}, Req, State}; %% NOTE - Not used at the moment, to be finished in next release % -info(#'Link'{id = LinkId, room_id = RoomId, type = Type, status = update = LStatus} = RequestData, +info(#'Link'{id = LinkId, room_id = RoomId, status = update = LStatus} = RequestData, Req, #cx{params = ClientId} = State) when RoomId /= [], LinkId /= [] -> - roster:info(?MODULE, "~p:Link/~p:~p", [ClientId, LStatus, RequestData]), + ?LOG_INFO("~p:Link/~p:~p", [ClientId, LStatus, RequestData]), Res = case roster:muc_member(ClientId, RoomId) of #'Member'{status = Status} when Status == owner; Status == admin -> case update_link(RequestData) of @@ -65,21 +66,21 @@ info(#'Link'{id = LinkId, room_id = RoomId, type = Type, status = update = LStat _ -> #io{code = #error{code = invalid_data}} end, - roster:info(?MODULE, "Link/~p.Response:~p", [Res, LStatus]), + ?LOG_INFO("Link/~p.Response:~p", [Res, LStatus]), {reply, {bert, Res}, Req, State}; %% NOTE - Not used at the moment, to be finished in next release % info(#'Link'{type = group, status = delete = LStatus} = RequestData, Req, #cx{params = ClientId} = State) -> - roster:info(?MODULE, "~p:Link/~p:~p", [ClientId, LStatus, RequestData]), + ?LOG_INFO("~p:Link/~p:~p", [ClientId, LStatus, RequestData]), % Res = delete_link(ClientId, RequestData), Res = #io{code = #error{code = permission_denied}}, - roster:info(?MODULE, "Link/~p.Response:~p", [Res, LStatus]), + ?LOG_INFO("Link/~p.Response:~p", [Res, LStatus]), {reply, {bert, Res}, Req, State}; info(#'Link'{} = RequestData, Req, #cx{params = ClientId} = State) -> - roster:info(?MODULE, "~p:Link/unknown:~p", [ClientId, RequestData]), + ?LOG_INFO("~p:Link/unknown:~p", [ClientId, RequestData]), {reply, {bert, #io{code = #error{code = invalid_data}}}, Req, State}. % ====================================== @@ -206,4 +207,4 @@ get_link({ok,#'Room'{id=EId}})-> E -> E end; get_link(ID) when is_binary(ID) -> get_link({ok,#'Room'{id=ID}}); -get_link(_)->{error, not_found}. \ No newline at end of file +get_link(_)->{error, not_found}. diff --git a/apps/roster/src/protocol/roster_message.erl b/apps/roster/src/protocol/roster_message.erl index a8d72c3e1619f9f1a7235d82266659f9a54c1bc2..6eb1054220268ced28668ff5c6055ba43df68c90 100644 --- a/apps/roster/src/protocol/roster_message.erl +++ b/apps/roster/src/protocol/roster_message.erl @@ -1,4 +1,5 @@ -module(roster_message). +-include_lib("kernel/include/logger.hrl"). -include("roster.hrl"). -include_lib("n2o/include/n2o.hrl"). -include_lib("kvs/include/kvs.hrl"). @@ -14,11 +15,11 @@ start() -> n2o_async:start(#handler{module = ?MODULE, class = system, group = ro info(#'Typing'{phone_id = Phone, comments = Comments}, Req, #cx{} = State) -> - roster:info(?MODULE, "~p:Typing/:~p", [Phone, Comments]), + ?LOG_INFO("~p:Typing/:~p", [Phone, Comments]), {reply, {bert, <<>>}, Req, State}; info(#'Message'{msg_id=MsgID, files=Descs, status=Status} = M, Req, #cx{state=[]}=State) when MsgID /= [], Status/=update-> - roster:info(?MODULE, "~p:~p:Message/:~p", [MsgID, State, Status]), + ?LOG_INFO("~p:~p:Message/:~p", [MsgID, State, Status]), case {kvs:index('Message', msg_id, MsgID), has_flag(Descs, message_ack)} of {[#'Message'{id = ServerId, feed_id = Feed, next = Next}], true} -> {reply, {bert, #'MessageAck'{id = ServerId, @@ -43,118 +44,131 @@ info(#'Message'{feed_id = #muc{name = To}, to = []} = RequestData, Req, State) w info(#'Message'{status = [], id = [], feed_id = F, from=From0, to = To, type = Type, msg_id = ClientMsgId, files = Descs} = Msg, Req, #cx{client_pid = C, params = ClientId, state=ack} = State) -> - From = case hd(binary:split(ClientId, <<"_">>)) of - <<"sys">> -> From0; - <<"emqttd">> -> roster:phone_id(ClientId) - end, + try + From = case hd(binary:split(ClientId, <<"_">>)) of + <<"sys">> -> From0; + <<"emqttd">> -> roster:phone_id(ClientId) + end, - FId = roster:roster_id(From), - - FeedData = {R, UID} = case Feed = roster:feed_key(F) of - _ when From == <<>> -> #error{code = permission_denied}; - #muc{name = To} -> - case roster:muc_member(From, To) of - #'Member'{status = removed} -> #error{code = permission_denied}; - #'Member'{reader = ReaderId} = Member -> {ReaderId, Member}; - _ -> #error{code = member_not_found} - end; - #p2p{} -> - case roster:get_contact(FId, To) of - #'Contact'{reader = ReaderId, status = friend} = Contact -> - {ReaderId, Contact}; - _ -> #error{code = permission_denied} - end; - _E -> #error{code = permission_denied} - end, - %% Without the transaction there is a race condition between loading the - %% writer and storing the updated writer. This caused the broken message - %% chains observed in NY-7274. - {atomic, IO} = mnesia:transaction(fun() -> - case R of - error -> - case has_flag(Descs, message_ack) of - true -> #io{code = #'MessageErr'{feed_id = F, - msg_id = ClientMsgId, - error = FeedData}}; - false -> #io{code = FeedData} - end; - _ when is_integer(R),R > 0-> - case roster:link_msg(Msg, UID) of - #error{} = Err -> - case has_flag(Descs, message_ack) of - true -> - #'MessageErr'{feed_id = F, - msg_id = ClientMsgId, - error = Err}; - false -> - #io{code = Err} - end; - LnkRes -> - PrevWriter2 = - case {Feed, PrevWriter = kvs_stream:load_writer(Feed)} of - {#p2p{from = From0, to = From0}, #writer{first = [], cache = []}} -> - ClnMsg = roster:desc_id(#'Message'{status = clear, feed_id = Feed, from = From0, to = From0, type = [sys], - files = [#'Desc'{payload = <<"History was removed">>}], created = roster:now_msec()}), - W = kvs_stream:save(kvs_stream:add(PrevWriter#writer{args = ClnMsg})), - roster:send_feed(C, Feed, W#writer.cache), W; - _ -> PrevWriter - end, -%% create message amd save to db - Writer = #writer{cache = #'Message'{id = MsgId} = M} = - kvs_stream:save(kvs_stream:add(PrevWriter2#writer{args = Msg#'Message'{container = chain, - feed_id = Feed, created = roster:now_msec()}})), - case Feed of - #muc{name = Room} -> - {ok, Room2} = kvs:get('Room', Room), - kvs:put(Room2#'Room'{last_msg = MsgId}); - _ -> skip end, - -%% move cursor - Reader = case lists:subtract(Type, [cursor]) of - Type -> kvs_stream:save(roster:offset_reader(Writer, R)); - _ -> kvs_stream:load_reader(R) - end, - roster:put_readers(write_top, UID, Reader), - LnkRes3 = case LnkRes of - #'Message'{prev = Prev, repliedby = ReplBy} -> - LnkRes2 = LnkRes#'Message'{repliedby = [MsgId | ReplBy], - prev = case Prev of [] -> MsgId; _ -> - Prev end}, %% get valid prev iterator - kvs:put(LnkRes2), LnkRes2; - _ -> LnkRes end, - roster:send_feed(C, F, Msg2 = M#'Message'{link = LnkRes3}), - roster:info(?MODULE, "~p:Message/new:~p", [From, To]), -%% have to skip push notifications for call bubbles - case [lists:keyfind(BubbleContentType, #'Desc'.mime, Descs) || BubbleContentType <- [?CONTENT_TYPE_VIDEOCALL, ?CONTENT_TYPE_AUDIOCALL]] of - [false, false] -> n2o_async:pid(system, ?MODULE) ! {send_push, From, To, Msg2, []}; - _ -> skip + {R, UID} = + case Feed = roster:feed_key(F) of + _ when From == <<>> -> + throw({error, permission_denied}); + #muc{name = To} -> + case roster:muc_member(From, To) of + #'Member'{status = removed} -> throw({error, permission_denied}); + #'Member'{reader = ReaderId} = Member -> {ReaderId, Member}; + _ -> throw({error, member_not_found}) + end; + #p2p{} -> + FId = roster:roster_id(From), + case roster:get_contact(FId, To) of + #'Contact'{reader = ReaderId, status = friend} = Contact -> + {ReaderId, Contact}; + _ -> + throw({error, permission_denied}) + end; + _E -> + throw({error, permission_denied}) + end, + + if R =< 0 -> throw({error, reader_not_found}); + true -> ok + end, + + + case roster:link_msg(Msg, UID) of + #error{} = Err1 -> + throw(Err1); + LinkRes -> + %% Without the transaction there is a race condition between loading the + %% writer and storing the updated writer. This caused the broken message + %% chains observed in NY-7274. + {atomic, {ToSend, Ack}} = mnesia:transaction(fun() -> + Writer0 = kvs_stream:load_writer(Feed), + {ToSend1, Writer1} = + case {Feed, Writer0} of + %% If this is the first message in the MeChat, add an initial message + {#p2p{from = From0, to = From0}, #writer{first = [], cache = []}} -> + FstMsg = #'Message'{status = clear, feed_id = Feed, + from = From0, to = From0, type = [sys], + files = [#'Desc'{payload = <<"History was removed">>}], + created = roster:now_msec()}, + W1 = kvs_stream:save( + kvs_stream:add(Writer0#writer{args = roster:desc_id(FstMsg)})), + {[W1#writer.cache], W1}; + _ -> + {[], Writer0} end, + + %% Create message and write it + Msg1 = Msg#'Message'{container = chain, feed_id = Feed, created = roster:now_msec()}, + Writer2 = #writer{cache = #'Message'{id = MsgId} = Msg2} = + kvs_stream:save(kvs_stream:add(Writer1#writer{args = Msg1})), + + %% If this is a MUC update last_msg + case Feed of + #muc{name = RoomId} -> + {ok, Room} = kvs:get('Room', RoomId), + kvs:put(Room#'Room'{last_msg = MsgId}); + #p2p{} -> + ok + end, + + %% Move cursor + case lists:member(cursor, Type) of + false -> kvs_stream:save(roster:offset_reader(Writer2, R)); + true -> ok + end, + + %% Update linked message + LinkRes1 = + case LinkRes of + #'Message'{ prev = Next0, repliedby = ReplBy } -> + Next = case Next0 of [] -> MsgId; _ -> Next0 end, + LM = LinkRes#'Message'{ repliedby = [MsgId | ReplBy], + prev = Next }, + kvs:put(LM), + LM; + _ -> + LinkRes + end, + Msg3 = Msg2#'Message'{link = LinkRes1}, + + Ack1 = case has_flag(Descs, message_ack) of - true -> - #'MessageAck'{id = MsgId, - next = Msg2#'Message'.next, - feed_id = Msg2#'Message'.feed_id, - msg_id = ClientMsgId, - created = Msg2#'Message'.created}; - false -> - <<>> - end - end; - _ -> - case has_flag(Descs, message_ack) of - true -> #io{code = #'MessageErr'{feed_id = F, - msg_id = ClientMsgId, - error = FeedData}}; - false -> #io{code = FeedData} - end - end end), - {reply, {bert, IO}, Req, State}; + true -> #'MessageAck'{id = MsgId, + next = Msg3#'Message'.next, + feed_id = Msg3#'Message'.feed_id, + msg_id = ClientMsgId, + created = Msg3#'Message'.created}; + false -> <<>> + end, + {ToSend1 ++ [Msg3], Ack1} + end), + + %% Send the message(s) after DB transaction + [ roster:send_feed(C, Feed, M) || M <- ToSend ], + + %% Maybe also push the message + PushM = lists:last(ToSend), + case [lists:keyfind(BubbleContentType, #'Desc'.mime, Descs) + || BubbleContentType <- [?CONTENT_TYPE_VIDEOCALL, ?CONTENT_TYPE_AUDIOCALL]] of + [false, false] -> n2o_async:pid(system, ?MODULE) ! {send_push, From, To, PushM, []}; + _ -> skip + end, + + {reply, {bert, Ack}, Req, State} + end + catch throw:#error{} = Err -> + {reply, {bert, #io{code = Err}}, Req, State} + end; info(#'Message'{status = edit, id = Id, msg_id = ClMID, feed_id = Feed, from = From, to = To, mentioned = Mentioned, - files = [#'Desc'{payload = Payload} | _] = Descs}, Req, + files = [#'Desc'{} | _] = Descs}, Req, #cx{params = ClientId, client_pid = C, state=ack} = State) -> PhoneId = roster:phone_id(ClientId, From), - roster:info(?MODULE, "~p:Message/edit:~p", [PhoneId, To]), + ?LOG_INFO("~p:Message/edit:~p", [PhoneId, To]), DV = length([D || D = #'Desc'{id = ID} <- Descs, is_binary(ID), ID /= <<>>]) == length(Descs), Data = case kvs:get('Message', Id) of @@ -166,7 +180,7 @@ info(#'Message'{status = edit, id = Id, msg_id = ClMID, feed_id = Feed, from = F _ -> #io{code = #error{code = not_found}} end; {ok, #'Message'{feed_id = #p2p{} = Feed, from = PhoneId, to = To} = Msg} when DV -> - % roster:info(?MODULE, "~p:~p:P2P/edit:~p",[Feed,From,Id]), + % ?LOG_INFO("~p:~p:P2P/edit:~p",[Feed,From,Id]), % Timeout=calendar:time_difference(roster:msToUT(T0),erlang:universaltime()), Cont = #'Contact'{reader = Rdr} = roster:get_contact(roster:roster_id(PhoneId), To), {Msg#'Message'{files = Descs}, Rdr, Cont}; @@ -176,21 +190,30 @@ info(#'Message'{status = edit, id = Id, msg_id = ClMID, feed_id = Feed, from = F IO = case Data of #io{} -> Data; - {#'Message'{from=From1, to = To1, type = Type, prev = Prev} = Message, Reader, Ent} -> -%% create system message - Smsg = roster:desc_id(#'Message'{status = edit, msg_id = ClMID, mentioned = Mentioned, type = NT = - case Type of [edited | _] -> Type; - [_ | _] -> [edited | Type]; - _ -> [edited] end, - feed_id = Feed, from = PhoneId, to = To1, link = Id, files = Descs, created = roster:now_msec()}), - #writer{cache = #'Message'{id = InternalId} = Internal} = kvs_stream:save(kvs_stream:add((kvs_stream:load_writer(Feed))#writer{args = Smsg})), + {#'Message'{from=From1, to = To1, type = Type, prev = Prev} = Message, Reader, _Ent} -> + %% create system message + NewType = + case Type of + [edited | _] -> Type; + [_ | _] -> [edited | Type]; + _ -> [edited] + end, + SMsg = roster:desc_id(#'Message'{status = edit, msg_id = ClMID, mentioned = Mentioned, + type = NewType, feed_id = Feed, from = PhoneId, + to = To1, link = Id, files = Descs, created = roster:now_msec()}), + Writer0 = kvs_stream:load_writer(Feed), + Writer = #writer{cache = #'Message'{id = InternalId} = Internal} = + kvs_stream:save(kvs_stream:add(Writer0#writer{args = SMsg})), M = Message#'Message'{prev = case Prev of [] -> InternalId; _ -> Prev end}, %% get valid prev iterator - kvs:put(M#'Message'{type = NT, files = Descs, mentioned = Mentioned}), -%% move cursor - kvs_stream:save(R = kvs_stream:top(kvs_stream:load_reader(Reader))), - roster:put_readers(write_top, Ent, R), + kvs:put(M#'Message'{type = NewType, files = Descs, mentioned = Mentioned}), + + %% move cursor + R0 = kvs_stream:load_reader(Reader), + R = roster:offset_reader(Writer, R0), + kvs_stream:save(R), n2o_async:pid(system, ?MODULE) ! {send_push, From1, To1, Internal, ?MSG_EDIT_ACTION}, -%% publish + + %% publish roster:send_feed(C, Feed, Internal), <<>>; E -> E end, @@ -199,7 +222,7 @@ info(#'Message'{status = edit, id = Id, msg_id = ClMID, feed_id = Feed, from = F info(#'Message'{id = Id, msg_id = ClMID, feed_id = Feed, from = From0, seenby = Seen, status = delete}, Req, #cx{params = ClientId, client_pid = C, state=ack} = State) when is_integer(Id) -> - roster:info(?MODULE, "~p:~p:Message/delete:~p", [Feed, From0, Id]), + ?LOG_INFO("~p:~p:Message/delete:~p", [Feed, From0, Id]), %%TODO for security From= PhoneId = roster:phone_id(ClientId, From0), D = case kvs:get('Message', Id) of @@ -212,7 +235,7 @@ info(#'Message'{id = Id, msg_id = ClMID, feed_id = Feed, from = From0, seenby = lists:usort(lists:subtract(Seen0, Seen) ++ lists:usort(Seen)) end, case roster:muc_member(PhoneId, Name) of #'Member'{reader = Rdr, status = admin} = Mem -> - %roster:info(?MODULE, "~p:~p:MUC/delete:~p",[Feed,From,Id]), + %?LOG_INFO("~p:~p:MUC/delete:~p",[Feed,From,Id]), {Msg, roster:room_topic(Name), Rdr, Mem, case {NSeen, Seen0} of {Seen0, Seen0} -> <<>>; @@ -233,7 +256,7 @@ info(#'Message'{id = Id, msg_id = ClMID, feed_id = Feed, from = From0, seenby = _ -> #io{code = #error{code = not_found}} end; {ok, #'Message'{feed_id = #p2p{} = Feed, from = From, to = To, created = T0, seenby = Seen0, status = []} = Msg}-> - % roster:info(?MODULE, "~p:~p:P2P/delete:~p",[Feed,From,Id]), + % ?LOG_INFO("~p:~p:P2P/delete:~p",[Feed,From,Id]), Timeout = calendar:time_difference(roster:msToUT(T0), erlang:universaltime()), Cont = #'Contact'{reader = Rdr} = roster:get_contact(roster:roster_id(PhoneId), case From == PhoneId of true -> To;false -> From end), @@ -250,18 +273,24 @@ info(#'Message'{id = Id, msg_id = ClMID, feed_id = Feed, from = From0, seenby = {reply, {bert, case D of {_, _, _, _, <<>>} -> #io{code = #error{code = invalid_data}}; - {#'Message'{id = MsgId, from = From1, to = To1, prev = Prev, type = Type, link = Link} = Message, Topic, Reader, Ent, NewSeen} -> - % create system message - Smsg = roster:desc_id(#'Message'{status = delete, msg_id = ClMID, feed_id = Feed, from = From1, to = To1, link = MsgId, seenby = NewSeen, - files = [#'Desc'{payload = <<"Delete">>}], created = roster:now_msec()}), - #writer{cache = Internal = #'Message'{id = InternalId}} - = kvs_stream:save(kvs_stream:add((kvs_stream:load_writer(Feed))#writer{args = Smsg})), - kvs:put(Message#'Message'{seenby = NewSeen, prev = case Prev of [] -> InternalId; _ -> - Prev end}), %% get valid prev iterator - % move cursor - kvs_stream:save(R = kvs_stream:top(kvs_stream:load_reader(Reader))), - roster:put_readers(write_top, Ent, R), - % publish + {#'Message'{id = MsgId, from = From1, to = To1, prev = Prev, type = Type, link = Link} = Message, Topic, Reader, _Ent, NewSeen} -> + %% create system message + SMsg = roster:desc_id(#'Message'{status = delete, msg_id = ClMID, feed_id = Feed, from = From1, + to = To1, link = MsgId, seenby = NewSeen, + files = [#'Desc'{payload = <<"Delete">>}], created = roster:now_msec()}), + Writer0 = kvs_stream:load_writer(Feed), + Writer = #writer{cache = Internal = #'Message'{id = InternalId}} + = kvs_stream:save(kvs_stream:add(Writer0#writer{args = SMsg})), + kvs:put(Message#'Message'{seenby = NewSeen, + %% get valid prev iterator + prev = case Prev of [] -> InternalId; _ -> Prev end}), + + %% move cursor + R0 = kvs_stream:load_reader(Reader), + R = roster:offset_reader(Writer, R0), + kvs_stream:save(R), + + %% publish %% TODO update last_msg in Room if the current deleted message is really last message in room case Feed of #muc{name = Room} -> case kvs:get('Room', Room) of @@ -280,7 +309,7 @@ info(#'Message'{id = Id, msg_id = ClMID, feed_id = Feed, from = From0, seenby = case {Type, Link} of {[reply], Link} when is_integer(Link) andalso NewSeen == [-1] -> case kvs:get('Message', Link) of - {error, not_found} -> roster:info(?MODULE, + {error, not_found} -> ?LOG_INFO( "link ~p for reply not found in ~p message", [Link, Message#'Message'.id]), <<>>; {ok, #'Message'{feed_id = Feed, repliedby = ReplBy} = LnkMessage} -> kvs:put(LnkMessage2 = LnkMessage#'Message'{repliedby = lists:delete(MsgId, ReplBy)}), @@ -294,7 +323,7 @@ info(#'Message'{status = update, type = [draft], feed_id = Feed0, from = From, t files = File}=M, Req, #cx{params = ClientId, client_pid = C, state=ack} = State) -> PhoneId = case ClientId of <<"sys_bpe">> -> From; <<"emqttd_", _/binary>> -> roster:phone_id(ClientId) end, - roster:info(?MODULE, "~p:Message/Draft:~p", [PhoneId, Feed0]), + ?LOG_INFO("~p:Message/Draft:~p", [PhoneId, Feed0]), {Feed,M1,D}=case File of [] -> {Feed0,[],#'Draft'{data =[M], status = delete}}; [#'Desc'{} | _] -> @@ -329,7 +358,7 @@ info(#'Message'{status = update, id = Id, files = [#'Desc'{mime = <<"transcribe" Pid = n2o_async:pid(system, roster_message), case kvs:get('Message', Id) of {ok, #'Message'{feed_id = Feed, from = From, to = To, files = [#'Desc'{mime = <<"audio">>, payload = Uri} | _]}} -> - roster:info(?MODULE, "enter ~p transcribe process with ~p", [Type, Uri]), + ?LOG_INFO("enter ~p transcribe process with ~p", [Type, Uri]), ErrMsg = #'Message'{id = Id, feed_id = Feed, from = From, to = To}, case Type of short -> @@ -345,7 +374,7 @@ info(#'Message'{status = update, id = Id, files = [#'Desc'{mime = <<"transcribe" end, [file:delete(filename:absname(File)) || File <- [FileIn, FileOut]], R; {error, ErrInfo} -> - roster:error(?MODULE, "invalid url for transcribe: ~p", [ErrInfo]), + ?LOG_ERROR("invalid url for transcribe: ~p", [ErrInfo]), #io{code = {error, invaid_data}, data = ErrMsg} end, Pid ! {Res, ClientId} end), {reply, {bert, #io{code = #ok{code = transcribe}, data = Id}}, Req, State}; @@ -361,22 +390,22 @@ info(#'Message'{status = update, id = Id, files = [#'Desc'{mime = <<"transcribe" Pid ! {Msg#'Message'{files = [Desc#'Desc'{payload = Text, data = Data2}]}, ClientId} end, google_api:transcribe(Type, GsUri, Lang, <<"ENCODING_UNSPECIFIED">>, Fun) end), {reply, {bert, #io{code = #ok{code = transcribe}, data = Id}}, Req, State}; - _ -> roster:error(?MODULE, "invalid transcribe type ~p", [Type]), + _ -> ?LOG_ERROR("invalid transcribe type ~p", [Type]), {reply, {bert, #io{code = #error{code = invalid_data}, data = ErrMsg}}, Req, State} end; {ok, InvalidMsg} -> - roster:info(?MODULE, "invalid audio transcribe for ~p", [InvalidMsg]), + ?LOG_INFO("invalid audio transcribe for ~p", [InvalidMsg]), {reply, {bert, #io{code = invalid_data, data = InvalidMsg}}, Req, State}; - {error, _} = E -> roster:error(?MODULE, "message ~p not found for transcribe", [Id]), + {error, _} = E -> ?LOG_ERROR("message ~p not found for transcribe", [Id]), {reply, {bert, #io{code = E}, #'Message'{id = Id}}, Req, State} end; -info(#'Message'{status = update, id = Id, feed_id = Feed, from = From, to = To, +info(#'Message'{status = update, id = Id, feed_id = Feed, from = From, files = [#'Desc'{id = ID, payload = Payload, data = Data, mime = DMime} = ND | _]}, Req, #cx{params = ClientId, client_pid = C, state=ack} = State) when is_integer(Id) -> PhoneId = case ClientId of <<"sys_bpe">> -> From; <<"emqttd_", _/binary>> -> roster:phone_id(ClientId) end, - roster:info(?MODULE, "~p:Message/update:~p", [PhoneId, Id]), + ?LOG_INFO("~p:Message/update:~p", [PhoneId, Id]), Lang = roster:get_data_val(?LANG_KEY, Data), IO = case kvs:get('Message', Id) of {ok, #'Message'{feed_id = Feed, files = Descs} = Msg} -> @@ -384,7 +413,7 @@ info(#'Message'{status = update, id = Id, feed_id = Feed, from = From, to = To, 1 -> Descs2 = lists:flatten(lists:foldr( fun (#'Desc'{data = NData, id = DescId} = D, [Acc, _]) when Lang == [], ID /= [] -> - FUsers = #'Feature'{value = I} = + #'Feature'{value = I} = case roster:get_data(?USERS_KEY, NData) of [] -> #'Feature'{key = ?USERS_KEY, value = []}; FU -> FU end, [[case DescId of @@ -425,11 +454,11 @@ info(#'Message'{status = update, id = Id, feed_id = Feed, from = From, to = To, info(#'Message'{from = From, to = To} = ReqData, Req, State) -> - roster:info(?MODULE, "~p:~p:Message/unknown:~p", [From, To, {ReqData, Req}]), + ?LOG_INFO("~p:~p:Message/unknown:~p", [From, To, {ReqData, Req}]), {reply, {bert, #io{code = #error{code = invalid_data}}}, Req, State}. proc(init, #handler{name = roster_message} = Async) -> - roster:info(?MODULE, "ASYNC:~p", [?MODULE]), + ?LOG_INFO("ASYNC:~p", [?MODULE]), {ok, C} = emqttc:start_link([{client_id, <<"roster_message">>}, {logger, {console, error}}, {reconnect, 5}]), {ok, Async#handler{state = C, seq = 0}}; @@ -442,27 +471,27 @@ proc({send_push, From, To, #'Message'{type = TypeList} = Msg, Action}, #handler{ true -> notify(From, To, Msg, Action); _ -> - roster:info(?MODULE, "ExcessivePush:~p", [From]) + ?LOG_INFO("ExcessivePush:~p", [From]) end; _ -> notify(From, To, Msg, Action) end, {reply, [], H}; proc({#io{} = IO, ClientId}, #handler{state = C} = H) -> - roster:error(?MODULE, "~p", [IO]), + ?LOG_ERROR("~p", [IO]), roster:send_action(C, ClientId, IO), {reply, [], H}; proc({#'Message'{status = update} = Msg, ClientId}, #handler{state = C} = H) -> - roster:info(?MODULE, "UPDATE TRANSCRIBE: ~p", [Msg]), + ?LOG_INFO("UPDATE TRANSCRIBE: ~p", [Msg]), try {reply, {bert, IO}, _, _} = info(Msg, {[], handled}, #cx{params = ClientId, client_pid = C, state = ack}), roster:send_action(C, ClientId, IO) catch Err:Rea -> - roster:error(?MODULE, ":~p~n", [n2o:stack_trace(Err, Rea)]) + ?LOG_ERROR(":~p~n", [n2o:stack_trace(Err, Rea)]) end, {reply, [], H}; proc({mqttc, C, connected}, State = #handler{state = C, seq = S}) -> {ok, State#handler{seq = S + 1}}; proc({mqttc, _C, disconnected}, State) -> {ok, State}; proc(Unknown, #handler{} = H) -> - roster:info(?MODULE, "invalid message data :~p", [Unknown]), + ?LOG_INFO("invalid message data :~p", [Unknown]), {reply, [], H}. diff --git a/apps/roster/src/protocol/roster_presence.erl b/apps/roster/src/protocol/roster_presence.erl index 9ecf2434cf49ffdc5770a463f37347de5830ee16..b4836e8cd9167a98d970c2616e12e65022350851 100644 --- a/apps/roster/src/protocol/roster_presence.erl +++ b/apps/roster/src/protocol/roster_presence.erl @@ -1,39 +1,35 @@ -module(roster_presence). +-include_lib("kernel/include/logger.hrl"). -include("roster.hrl"). -include_lib("n2o/include/n2o.hrl"). -include_lib("rest_static.hrl"). -compile(export_all). -info(#'Presence'{uid=UID, status=Status} = RequestData, Req, #cx{params = ClientId, client_pid = C} = State) -> - roster:info(?MODULE, "~p:Presence/Status:~p", [ClientId, RequestData]), - {reply, {bert, send_presence(Status, roster:phone_id(ClientId), C)}, Req, State}; - - -info(#'Presence'{} = RequestData, Req, #cx{params = ClientId} = State) -> - roster:info(?MODULE, "~p:Presence/unknown:~p", [ClientId, RequestData]), - {reply, {bert, #io{code = #error{code = invalid_data}}}, Req, State}. +info(#'Presence'{status=Status} = RequestData, Req, #cx{params = ClientId, client_pid = C} = State) -> + ?LOG_INFO("~p:Presence/Status:~p", [ClientId, RequestData]), + {reply, {bert, send_presence(Status, roster:phone_id(ClientId), C)}, Req, State}. on_connect(Phone, ClientId, C)-> on_connect(Phone, ClientId, C, ?VERSION). on_connect(Phone, ClientId, C, Ver) -> - roster:info(?MODULE, "~p:~p:CONNECT with Version:~p", [Phone, ClientId,Ver]), + ?LOG_INFO("~p:~p:CONNECT with Version:~p", [Phone, ClientId,Ver]), catch n2o_vnode:unsubscribe(ClientId, iolist_to_binary(["actions/", "1", "/micro/", ClientId])), try case send_presence(online, Phone, C, ClientId) of - {error, profile_not_found} -> roster:info(?MODULE, "~p:~p:Connect:ProfileNotFound", [Phone, ClientId]), + {error, profile_not_found} -> ?LOG_INFO("~p:~p:Connect:ProfileNotFound", [Phone, ClientId]), roster:delete_sessions(Phone); - #'Profile'{} = P when Ver==?VERSION -> + #'Profile'{} when Ver==?VERSION -> roster:send_cache(ClientId, C); #'Profile'{} = P-> roster:send_profile(P#'Profile'{settings = amazon_settings(P), status = init}, [], 0, ClientId, C), roster:send_cache(ClientId, C) end catch Err:Rea -> - roster:info(?MODULE, "Catch:~p", [n2o:stack_trace(Err, Rea)]) + ?LOG_INFO("Catch:~p", [n2o:stack_trace(Err, Rea)]) end. on_disconnect(#'Auth'{type = logout, phone = Phone, client_id = ClientId, user_id = PhoneId}, C) -> - roster:info(?MODULE, "~p:~p:DISCONNECT:LOGOUT", [Phone, ClientId]), + ?LOG_INFO("~p:~p:DISCONNECT:LOGOUT", [Phone, ClientId]), send_presence(offline, Phone, C, ClientId), roster:unsubscribe_p2p(ClientId, roster:roster_id(PhoneId)), roster:unsubscribe_room(ClientId), @@ -41,7 +37,7 @@ on_disconnect(#'Auth'{type = logout, phone = Phone, client_id = ClientId, user_i roster:final_disconnect(ClientId); on_disconnect(#'Auth'{type = disconnect, phone = Phone, client_id = ClientId, user_id = PhoneId}, C) -> - roster:info(?MODULE, "~p:~p:DISCONNECT", [Phone, ClientId]), + ?LOG_INFO("~p:~p:DISCONNECT", [Phone, ClientId]), send_presence(offline, Phone, C, ClientId), roster:unsubscribe_p2p(ClientId, roster:roster_id(PhoneId)), roster:unsubscribe_room(ClientId), @@ -52,10 +48,10 @@ on_disconnect(#'Auth'{type = disconnect, phone = Phone, client_id = ClientId, us roster:final_disconnect(ClientId); on_disconnect(#'Auth'{phone = Phone, client_id = ClientId, type=Type}, C) -> - roster:info(?MODULE, "~p:~p:DISCONNECT:~p", [Phone, ClientId, Type]), + ?LOG_INFO("~p:~p:DISCONNECT:~p", [Phone, ClientId, Type]), send_presence(offline, Phone, C, ClientId). -on_verify(ClientId, PhoneId) -> roster:sub_client(subscribe, ClientId, PhoneId). +on_verify(_ClientId, _PhoneId) -> ok. %% Warning: Do not always delete Auth record, it holds push notification tokens should_preserve_auth_record(ClientId) -> @@ -82,7 +78,7 @@ send_presence(Status, Phone, C, ClientId) when Status == online; Status == offli end || AccId <- Rosters], P2; _ -> - roster:info(?MODULE, "~p:~p:~p",[Phone, ClientId,P]), + ?LOG_INFO("~p:~p:~p",[Phone, ClientId,P]), P end; _ -> {error, profile_not_found} @@ -92,7 +88,7 @@ send_presence(Status, Phone, C, ClientId) when Status == online; Status == offli send_presence(Status, PhoneId, C) -> case kvs:get('Profile', roster:phone(PhoneId)) of {ok, #'Profile'{rosters = Rosters} = P} -> - kvs:put(P#'Profile'{presence = Status, update = Now = roster:now_msec()}), + kvs:put(P#'Profile'{presence = Status, update = roster:now_msec()}), [case kvs:get('Roster', AccId) of {ok, #'Roster'{phone = Phone}} -> PId = roster:phone_id(Phone, AccId), @@ -107,4 +103,4 @@ amazon_settings(#'Profile'{phone = ProfileId, settings = Settings}) -> StaticFeatures = lists:foldl(fun({Key, Value}, Acc) -> Acc ++ [#'Feature'{id = iolist_to_binary([ProfileId, Key]), key = Key, value = Value, group = <<"SPECIAL_ANDROID_KEYS">>}] end, [], [{<<"ACCESS_KEY">>, ?AWS_ACCESS_KEY_ID}, {<<"SECRET_KEY">>, ?AWS_SECRET_ACCESS_KEY}]), - Settings ++ StaticFeatures. \ No newline at end of file + Settings ++ StaticFeatures. diff --git a/apps/roster/src/protocol/roster_profile.erl b/apps/roster/src/protocol/roster_profile.erl index b9f85cbe99db137926b685e07e3b7fd407990760..66d270a124b96292c1ab3b199d4e69da6e3353ed 100644 --- a/apps/roster/src/protocol/roster_profile.erl +++ b/apps/roster/src/protocol/roster_profile.erl @@ -1,4 +1,5 @@ -module(roster_profile). +-include_lib("kernel/include/logger.hrl"). -include("roster.hrl"). -include_lib("n2o/include/n2o.hrl"). -compile(export_all). @@ -16,7 +17,7 @@ start() -> n2o_async:start(#handler{ %%info(#'Profile'{phone=Phone, rosters=Rosters, status=set} = Data, Req, %% #cx{params = ClientId} = State) -> -%% roster:info(?MODULE, "~p:~p:Profile/set:~s", [Phone,ClientId,io_lib:format("~p",[Data])]), +%% ?LOG_INFO("~p:~p:Profile/set:~s", [Phone,ClientId,io_lib:format("~p",[Data])]), %% D=case roster:parts_phone_id(roster:phone_id(ClientId)) of %% [Phone, RosterId]-> kvs:put(Data2 = Data#'Profile'{rosters=[RosterId],update = roster:now_msec()}), Data2; %% _ -> #io{code=#error{code=invalid_data}} end, @@ -24,7 +25,7 @@ start() -> n2o_async:start(#handler{ info(#'Profile'{status=patch, phone=Phone, balance = Balance} = Data, Req, #cx{params = ClientId} = State) when Balance >= 0 -> - roster:info(?MODULE, "~p:~p:Profile/patch", [Phone,ClientId]), + ?LOG_INFO("~p:~p:Profile/patch", [Phone,ClientId]), Res = case kvs:get('Profile', Phone) of {ok, #'Profile'{}=Profile} -> P = roster:patch_profile(Profile, Data), @@ -35,7 +36,7 @@ info(#'Profile'{status=patch, phone=Phone, balance = Balance} = Data, Req, info(#'Profile'{status = delete, phone=UID, rosters = [_|_]=Rs }=Data, Req, #cx{params = ClientId, client_pid = C} = State) -> - roster:info(?MODULE, "~p:~p:Profile/remove:~s", [UID,ClientId,io_lib:format("~p",[Data])]), + ?LOG_INFO("~p:~p:Profile/remove:~s", [UID,ClientId,io_lib:format("~p",[Data])]), case kvs:get('Profile',UID) of {ok,#'Profile'{rosters=Rosters}=P}-> NewRosters=(Rosters--Rs), @@ -57,7 +58,7 @@ info(#'Profile'{status = delete, phone=UID, rosters = [_|_]=Rs }=Data, Req, info(#'Profile'{status = remove, phone = Phone}=Data, Req, #cx{params = ClientId, client_pid = C} = State) -> - roster:info(?MODULE, "~p:~p:Profile/remove:~s", [Phone,ClientId,io_lib:format("~p",[Data])]), + ?LOG_INFO("~p:~p:Profile/remove:~s", [Phone,ClientId,io_lib:format("~p",[Data])]), case kvs:get('Profile',Phone) of {ok,#'Profile'{rosters=Rosters}} -> [ begin PhoneId=roster:phone_id(Phone,X), [begin [PhoneF, _]=roster:parts_phone_id(Friend), @@ -80,9 +81,9 @@ info(#'Profile'{ params = ClientId, client_pid = ClientPid } = State) -> - roster:info(?MODULE, "~p:Profile/get.Request/enter:~p", [ClientId, P2]), + ?LOG_INFO("~p:Profile/get.Request/enter:~p", [ClientId, P2]), Phone = roster:phone(ClientId), - roster:info(?MODULE, "~p:Profile/get.Request:~p", [ClientId, Phone]), + ?LOG_INFO("~p:Profile/get.Request:~p", [ClientId, Phone]), R = case kvs:get('Profile', Phone) of {ok, #'Profile'{} = P} -> Size = case {LastSync, catch binary_to_integer(roster:get_data_val(<<"size">>, Settings))} of @@ -102,7 +103,7 @@ info(#'Profile'{ ClientId, ClientPid ]), - roster:info(?MODULE, "timer_send_profile in microseconds ~p", [Timer]), + ?LOG_INFO("timer_send_profile in microseconds ~p", [Timer]), <<>>; {error, Reason} -> #io{code = #error{code = Reason}} @@ -117,11 +118,11 @@ info(#'Profile'{} = Data, Req, State) -> {reply, {bert, Data}, Req, State}. proc(init,#handler{name=?MODULE} = Async) -> - roster:info(?MODULE, "ASYNC",[]), + ?LOG_INFO("ASYNC",[]), {ok,Async}; -proc({restart, M}, #handler{state = {C, Proc}, seq = S} = H) -> - roster:info(?MODULE, "BPE PROC restarted", []), +proc({restart, M}, #handler{state = {_C, _Proc}} = H) -> + ?LOG_INFO("BPE PROC restarted", []), roster:restart_module(M), {reply, [], H}. @@ -145,10 +146,10 @@ proc({restart, M}, #handler{state = {C, Proc}, seq = S} = H) -> %% #io{code = #ok{code = aws}, data = #'Service'{id = AccessKeyId, type = aws, login = SecretAccessKey, %% password = SessionToken, expiration = Expiration, status = verified}} %% end, -%% roster:info(?MODULE, "Debug.AWSProcResponse:~p", [Res]), +%% ?LOG_INFO("Debug.AWSProcResponse:~p", [Res]), %% case emqttd_cm:lookup(ClientId) of %% undefined -> -%% roster:info(?MODULE, "CannotFindConnection:~p", [ClientId]); +%% ?LOG_INFO("CannotFindConnection:~p", [ClientId]); %% _-> roster:send_action(C, ClientId, Res) %% end, %% {reply, [], H}. diff --git a/apps/roster/src/protocol/roster_push.erl b/apps/roster/src/protocol/roster_push.erl index 961ea4961c1d28ae5f7be2ad918e3bdb8bea1581..b6e67162c5aee7831287e778536091a670e3a239 100644 --- a/apps/roster/src/protocol/roster_push.erl +++ b/apps/roster/src/protocol/roster_push.erl @@ -1,4 +1,5 @@ -module(roster_push). +-include_lib("kernel/include/logger.hrl"). -include("roster.hrl"). -include_lib("n2o/include/n2o.hrl"). -include_lib("kvs/include/kvs.hrl"). @@ -7,7 +8,7 @@ start() -> n2o_async:start(#handler{module = ?MODULE, class = system, group = roster, name = ?MODULE, state = []}). proc(init, #handler{name = ?MODULE} = Async) -> - roster:info(?MODULE, "ASYNC", []), + ?LOG_INFO("ASYNC", []), {ok, Async}; proc({async_push, Session, Payload, PushAlert, PushType}, #handler{} = H) -> @@ -19,7 +20,7 @@ send_push_notification(#'Auth'{os = OS, push = PushToken, user_id = PhoneId, set case PushToken of [] -> skip; _ -> - roster:info(?MODULE, "~p:~p:~pPushAlert:~p", + ?LOG_INFO("~p:~p:~pPushAlert:~p", [PhoneId, OS, binary:part(PushToken, 0, erlang:min(25, size(PushToken))), PushAlert]), send_push_notification(OS, PushToken, Payload, PushAlert, PushType, AuthSettings) end. diff --git a/apps/roster/src/protocol/roster_room.erl b/apps/roster/src/protocol/roster_room.erl index c61549e5b68f4106fe05fa482ff2d8e8c78bc9a4..cf199c40e028d991105f93ede0b1e6181810b044 100644 --- a/apps/roster/src/protocol/roster_room.erl +++ b/apps/roster/src/protocol/roster_room.erl @@ -1,4 +1,5 @@ -module(roster_room). +-include_lib("kernel/include/logger.hrl"). -include("roster.hrl"). -include_lib("n2o/include/n2o.hrl"). -include_lib("roster/include/static/roster_text.hrl"). @@ -20,24 +21,24 @@ info(#'Room'{status = create, id = Room, admins = [Admin|_], type=Type} = R, Req ok = kvs:put(Link), info(R#'Room'{status = add, id = Room}, Req, State#cx{state = create}); -info(#'Room'{status = create, id = <>, name = Name, admins = [Owner]= Admins, members = [], data = Data} = R, Req, +info(#'Room'{status = create, id = <>, name = Name, admins = [_Owner], members = []} = R, Req, #cx{params = <<"emqttd_", _/binary>>=ClientId} = State) -> Length = length(unicode:characters_to_list(Name)), ExistingRoom = element(1, kvs:get('Room', Room)) == error, - roster:info(?MODULE, "~p:Room/create single room:~p", [ClientId, Name]), + ?LOG_INFO("~p:Room/create single room:~p", [ClientId, Name]), if not(Length >= ?MIN_ROOM_LENGTH) -> {reply, {bert, #io{code = #error{code = room_name_too_short}}}, Req, State}; not(Length =< ?MAX_ROOM_LENGTH) -> {reply, {bert, #io{code = #error{code = room_name_too_long}}}, Req, State}; not(ExistingRoom) -> {reply, {bert, #io{code = #error{code = room_already_exists}}}, Req, State}; - true -> #'Member'{phone_id = OwnPId} = Adm = #'Member'{phone_id = roster:phone_id(ClientId)}, + true -> #'Member'{phone_id =_OwnPId} = Adm = #'Member'{phone_id = roster:phone_id(ClientId)}, Admin = Adm#'Member'{id = [], status = admin, feed_id = #muc{name = Room}, update = roster:now_msec()}, info(R#'Room'{admins = [Admin], members = []}, Req, State#cx{state = verified}) end; -info(#'Room'{status = create, id = <>, name = Name, admins = [Owner|TA]= Admins, members = Members, data = Data} = R, Req, +info(#'Room'{status = create, id = <>, name = Name, admins = [Owner|TA]= Admins, members = Members} = R, Req, #cx{params = ClientId} = State) -> Length = length(unicode:characters_to_list(Name)), - roster:info(?MODULE, "~p:Room/create:~p", [ClientId, Name]), + ?LOG_INFO("~p:Room/create:~p", [ClientId, Name]), Prefix = hd(binary:split(ClientId, <<"_">>)), case Length >= ?MIN_ROOM_LENGTH andalso Length =< ?MAX_ROOM_LENGTH andalso element(1, kvs:get('Room', Room)) == error @@ -59,7 +60,7 @@ info(#'Room'{status = create, id = <>, name = Name, admins = [Owner _ -> {reply, {bert, #io{code = #error{code = invalid_data}}}, Req, State} end; info(#'Room'{status = patch, id = Room, name = Name, data = AvatarDesc} = R, Req, #cx{params = ClientId, client_pid = C} = State) when Room /= [] -> - roster:info(?MODULE, "~p:Room/patch:~p", [ClientId, R]), + ?LOG_INFO("~p:Room/patch:~p", [ClientId, R]), roster:verify_descs(AvatarDesc), {reply, {bert, case kvs:get('Room', Room) of @@ -115,7 +116,7 @@ info(#'Room'{status = patch, id = Room, name = Name, data = AvatarDesc} = R, Req end}, Req, State}; info(#'Member'{status = patch, id = Id} = Member, Req, #cx{params = ClientId, from = From} = State) -> - roster:info(?MODULE, "~p:Member/patch:~p", [ClientId, Member]), + ?LOG_INFO("~p:Member/patch:~p", [ClientId, Member]), PhoneId = roster:phone_id(ClientId), {IO, Topic} = case kvs:get('Member', Id) of @@ -130,7 +131,7 @@ info(#'Room'{status = St, id = Room, members = Members, admins = Admins, readers #cx{params = ClientId, client_pid = C, state = RStatus} = State) when (St == add orelse (St == join andalso RStatus /= create)) andalso (Members /= [] orelse Admins /= []) -> - roster:info(?MODULE, "~p:Room/~p:~p", [ClientId, St, Room]), + ?LOG_INFO("~p:Room/~p:~p", [ClientId, St, Room]), APId = case Prefix = hd(binary:split(ClientId, <<"_">>)) of <<"sys">> when St == add andalso Admins /= [] orelse St == join -> (hd(case St of join -> Members; _ -> Admins end))#'Member'.phone_id; @@ -145,7 +146,7 @@ info(#'Room'{status = St, id = Room, members = Members, admins = Admins, readers Mmbr#'Member'.status == removed andalso Prefix == <<"sys">>) -> {Mmbrs, Aliases, NewMmbrs, StoredRoom2} = lists:foldl( - fun(#'Member'{phone_id = PhoneId, status = Status} = Member, {Ms, Alss, NewMs, TmpRoom} = MAcc) + fun(#'Member'{phone_id = PhoneId, status = Status} = Member, {Ms, Alss, NewMs, TmpRoom}) when Status == admin; Status == member -> MmbrRoom = case roster:muc_member(PhoneId, Room, presence) of @@ -158,18 +159,17 @@ info(#'Room'{status = St, id = Room, members = Members, admins = Admins, readers #'Member'{} = M -> kvs:put(M2 = roster:patch_member(M#'Member'{status = Status}, Member)), {ignore, M2}; - _ -> {M2, _, UpdRoom} = roster:add_member(TmpRoom, + _ -> + {M2, _, UpdRoom} = roster:add_member(TmpRoom, Member#'Member'{feed_id = #muc{name = Room}, reader = 0}, {no_muc_message, HL}), {M2, UpdRoom} end, - case MmbrRoom of error -> - roster:info(?MODULE, "ERROR:add_member:RosterNotFound:~p", [PhoneId]), - MAcc; + case MmbrRoom of {ignore, M5} -> {Ms ++ [M5#'Member'{presence = roster:is_online(PhoneId)}], Alss, NewMs, StoredRoom}; {#'Member'{alias = Alias} = M4, UpdRoom2} -> {Ms ++ [M4#'Member'{presence = roster:is_online(PhoneId)}], - Alss ++ [Alias, <<",">>], NewMs ++ [M4], UpdRoom2} end + Alss ++ [Alias, <<",">>], NewMs ++ [M4], UpdRoom2} end end, {[], [], [], StoredRoom}, Admins ++ Members), {Admins2, Members2} = roster:split_members(Mmbrs), {Payload, RStatus2} = @@ -180,7 +180,7 @@ info(#'Room'{status = St, id = Room, members = Members, admins = Admins, readers {_, _, join} -> {<>, join}; _ -> {iolist_to_binary(["added by ", APId, ": ", lists:droplast(Aliases)]), add} end, - {AdmMember = #'Member'{reader = Reader}, Admins3} = + {#'Member'{reader = Reader}, Admins3} = case {Mmbr, lists:keyfind(APId, #'Member'.phone_id, Admins2)} of {[], false} when St == join -> {hd(Members2), []}; {#'Member'{status = removed}, _} when St == join -> {hd(Members2), []}; @@ -190,37 +190,50 @@ info(#'Room'{status = St, id = Room, members = Members, admins = Admins, readers R = StoredRoom2#'Room'{update = roster:now_msec()}, - R2 = case Mmbrs of + R2 = + case Mmbrs of [] -> R; - _ -> {R3, R4} = - case Aliases of - [] -> - {R#'Room'{last_msg = element(2, kvs:get('Message', R#'Room'.last_msg))}, R}; - _ -> Msg = roster:add_message( - #'Message'{status = [], type = [sys], feed_id = #muc{name = Room}, from = APId, - to = Room, msg_id = roster:msg_id(), - files = [#'Desc'{payload = Payload}]}, Reader), - {_, PutRoom} = roster:put_readers(write_top, AdmMember, R), - n2o_async:pid(system, ?MODULE) ! {send_push, NewMmbrs, Msg, RStatus2}, - {PutRoom#'Room'{last_msg = Msg}, PutRoom#'Room'{last_msg = Msg#'Message'.id}} end, - R5 = R3#'Room'{status = RStatus2, members = Members2, admins = Admins3, links=[roster_link:get_link({ok,R3})]}, - [ begin roster:send_ses(C, roster:phone(PhId), - roster:reader_cache(R5#'Room'{unread = - case {RStatus, lists:keymember(MembId, #'Member'.id, Mmbrs)} of - {create, _} when PhId == APId -> 0; {create, true} -> 1; - _ -> element(1, roster:unread_msg(R3#'Room'.last_msg, Rdr, PhId)) - end})) end - || #'Member'{id = MembId, phone_id = PhId, reader = Rdr, status = SST} - <- roster:members(#muc{name = Room}), SST /= removed], - R4 end, - kvs:put(R2), <<>>; + _ -> + {Rx1, LastMsg} = + case Aliases of + [] -> + {ok, LMsg} = kvs:get('Message', R#'Room'.last_msg), + {R, LMsg}; + _ -> + Msg0 = #'Message'{ status = [], type = [sys], feed_id = #muc{name = Room}, + from = APId, to = Room, msg_id = roster:msg_id(), + files = [#'Desc'{payload = Payload}] }, + Msg = roster:add_message(Msg0, Reader), + n2o_async:pid(system, ?MODULE) ! {send_push, NewMmbrs, Msg, RStatus2}, + {R, Msg} + end, + Rx2 = Rx1#'Room'{status = RStatus2, members = Members2, admins = Admins3, + links = [roster_link:get_link({ok, Rx1})], last_msg = LastMsg}, + + [ begin + Unread = + case {RStatus, lists:keymember(MembId, #'Member'.id, Mmbrs)} of + {create, _} when PhId == APId -> 0; + {create, true} -> 1; + _ -> + {Unread0, _, _} = roster:unread_msg(LastMsg, Rdr, PhId), + Unread0 + end, + roster:send_ses(C, roster:phone(PhId), roster:reader_cache(Rx2#'Room'{unread = Unread})) + end || #'Member'{id = MembId, phone_id = PhId, reader = Rdr, status = SST} + <- roster:members(#muc{name = Room}), SST /= removed], + + Rx1#'Room'{last_msg = LastMsg#'Message'.id} + end, + kvs:put(R2), + <<>>; _ -> #io{code = #error{code = permission_denied}} end; {error, _} -> #io{code = #error{code = room_not_found}} end, {reply, {bert, IO}, Req, State}; info(#'Room'{status = remove, members = Members, admins = Admins0, id = Room}, Req, #cx{params = ClientId, client_pid = C} = State) -> - roster:info(?MODULE, "~p:Room/remove:~p", [ClientId, Room]), + ?LOG_INFO("~p:Room/remove:~p", [ClientId, Room]), {APId, Admins} = case hd(binary:split(ClientId, <<"_">>)) of <<"sys">> -> {(hd(Admins0))#'Member'.phone_id, []}; <<"emqttd">> -> {roster:phone_id(ClientId), Admins0} @@ -237,18 +250,19 @@ info(#'Room'{status = remove, members = Members, admins = Admins0, id = Room}, R #'Member'{feed_id = Feed, alias = Alias} = M -> roster:unsubscribe_room(M), %% Unsubscribe this member from the room. {ok, #'Roster'{roomlist = Rooms} = Roster} = kvs:get('Roster', roster:roster_id(PhoneId)), - roster:unsubscribe_muc(Roster2 = Roster#'Roster'{roomlist = - lists:ukeymerge(#'Room'.id, [StoredRoom#'Room'{status = removed}], Rooms)}, Feed), + RoomList = lists:ukeymerge(#'Room'.id, [StoredRoom#'Room'{status = removed}], Rooms), + Roster2 = Roster#'Roster'{roomlist = RoomList}, + roster:unsubscribe_muc(Roster2, Feed), kvs:put(M2 = M#'Member'{status = removed, update = roster:now_msec()}), {[M2 | Ms], [Alias, <<",">> | Alss], [Roster2 | Rs]}; - false -> Acc end end, {[], [], []}, Members ++ Admins), + false -> Acc + end end, {[], [], []}, Members ++ Admins), {Admins2, Members2} = roster:split_members(Mmbrs), Admins3 = lists:ukeymerge(#'Member'.id, lists:ukeysort(#'Member'.id, Admins2), [AdmMember]), Msg = roster:add_message(#'Message'{feed_id = #muc{name = Room}, from = APId, to = Room, msg_id = roster:msg_id(), status = [], type = [sys], files = [#'Desc'{payload = iolist_to_binary([APId, <<" removed: ">>, lists:droplast(Aliases)])}]}, Reader), - {_, StoredRoom2} = roster:put_readers(write_top, AdmMember, StoredRoom), - kvs:put(R = StoredRoom2#'Room'{update = roster:now_msec(), last_msg = Msg#'Message'.id}), + kvs:put(R = StoredRoom#'Room'{update = roster:now_msec(), last_msg = Msg#'Message'.id}), [roster:update_rooms(Rstr, R#'Room'{status = removed}) || Rstr <- Rosters], R2 = roster:reader_cache(R#'Room'{status = remove, last_msg = [], members = Members2, admins = Admins3}), @@ -262,46 +276,50 @@ info(#'Room'{status = remove, members = Members, admins = Admins0, id = Room}, R {error, _} -> #io{code = #error{code = room_not_found}} end, {reply, {bert, IO}, Req, State}; -info(#'Room'{status = delete, members = [], admins = [], id = Room} = R, Req, - #cx{params = <<"sys_", _/binary>> = ClientId, client_pid = C} = State) -> - roster:info(?MODULE, "~p:Room/delete:~p", [ClientId, Room]), - case kvs_stream:load_writer(Feed = #muc{name = Room}) of - #writer{first = #'Message'{id = FirstMsgId}, cache = #'Message'{id = LastMsgId}} -> - FilterFun = fun(#'Message'{type = [sys|_]}, _Acc) -> true; (_, _Acc) -> false end, - StopFun = fun(_, false) -> 0; (_M, _A) -> 1 end, - Now = roster:now_msec(), - IsMsg = roster:fold(FilterFun, true, 'Message', LastMsgId, FirstMsgId, - #kvs{mod = store_mnesia}, #iterator.prev, StopFun), - [case IsMsg of - true -> - roster:remove_rooms(PhoneId, [Room]), - kvs:delete(reader, Reader), - kvs:put(Member#'Member'{reader = 0, update = roster:now_msec(), status = removed}), - roster:send_ses(C, roster:phone(PhoneId), R#'Room'{update = Now}); - _ -> - {ok, Roster = #'Roster'{roomlist = Rooms}} = kvs:get('Roster', roster:roster_id(PhoneId)), - RoomRoster = lists:keyfind(Room, #'Room'.id, Rooms), - roster:update_rooms(Roster, RoomRoster#'Room'{type = group}) - end || #'Member'{reader = Reader, phone_id = PhoneId} = Member <- roster:members(Feed), Reader/=0], - {ok, R2} = kvs:get('Room', Room), - kvs:put(case IsMsg of true -> R2#'Room'{status = delete}; _-> R2#'Room'{type = group} end), - skip; - _ -> skip +info(#'Room'{status = delete, members = [], admins = [], id = RoomId} = Room, Req, + #cx{params = <<"sys_", _/binary>> = ClientId, client_pid = C} = State) -> + ?LOG_INFO("~p:Room/delete:~p", [ClientId, RoomId]), + case kvs_stream:load_writer(Feed = #muc{name = RoomId}) of + #writer{cache = #'Message'{id = LastMsgId}} -> + Fun = fun(#'Message'{type = [sys | _]}, _) -> {cont, false}; + (#'Message'{}, _) -> {stop, true} + end, + {_, IsMsg} = roster:fold_stream('Message', Fun, LastMsgId, false, bwd), + Now = roster:now_msec(), + {ok, Room2} = kvs:get('Room', RoomId), + [case IsMsg of + true -> + roster:remove_rooms(PhoneId, [RoomId]), + kvs:delete(reader, Reader), + kvs:put(Member#'Member'{reader = 0, update = Now, status = removed}), + [ roster:send_ses(C, roster:phone(PhoneId), + Room#'Room'{update = Now, name = Room2#'Room'.name}) + || Member#'Member'.status /= removed ]; + _ -> + {ok, Roster = #'Roster'{roomlist = Rooms}} = kvs:get('Roster', roster:roster_id(PhoneId)), + RoomR = lists:keyfind(RoomId, #'Room'.id, Rooms), + roster:update_rooms(Roster, RoomR#'Room'{type = group}) + end || #'Member'{reader = Reader, phone_id = PhoneId } = Member <- roster:members(Feed), Reader /= 0], + + Room3 = if IsMsg -> Room2#'Room'{status = delete, update = Now}; + true -> Room2#'Room'{type = group, update = Now} + end, + kvs:put(Room3); + _ -> + ok end, {reply, {bert, <<>>}, Req, State}; info(#'Room'{status = leave, id = RoomId}, Req, #cx{params = ClientId, client_pid = C} = State) -> - roster:info(?MODULE, "~p:Room/leave:~p", [ClientId, RoomId]), + ?LOG_INFO("~p:Room/leave:~p", [ClientId, RoomId]), {reply, {bert, case kvs:get('Room', RoomId) of - {ok, #'Room'{} = Room0} -> + {ok, #'Room'{} = Room1} -> MUCMembers = roster:members(#muc{ name = RoomId }), Admins = roster:filter_members(MUCMembers, admin), case lists:keyfind(roster:phone_id(ClientId), #'Member'.phone_id, MUCMembers) of - #'Member'{phone_id = PhoneId, status = Status, reader = Reader} = M when [M] /= Admins -> + #'Member'{phone_id = PhoneId, status = Status} = M when [M] /= Admins -> roster:remove_rooms(ClientId, [RoomId]), - kvs:delete(reader, Reader), - Room1 = roster:sort_readers(Room0), Room2 = case Status of admin -> Room1#'Room'{ admins = [M] }; _ -> Room1#'Room'{ members = [M] } @@ -325,7 +343,7 @@ info(#'Room'{status = leave, id = RoomId}, Req, #cx{params = ClientId, client_pi n2o_async:pid(system, ?MODULE) ! {send_push, [], Msg, leave}, Room2#'Room'{update = roster:now_msec(), last_msg = Msg#'Message'.id} end, - kvs:put(M#'Member'{reader = 0, update = roster:now_msec(), status = removed}), + kvs:put(M#'Member'{update = roster:now_msec(), status = removed}), kvs:put(Room5), <<>>; #'Member'{} -> #io{code = #error{ code = last_admin_cant_leave }}; @@ -335,7 +353,7 @@ info(#'Room'{status = leave, id = RoomId}, Req, #cx{params = ClientId, client_pi end}, Req, State}; info(#'Room'{status = get, id = Room}, Req, #cx{params = ClientId} = State) -> - roster:info(?MODULE, "~p:Room/get:~p", [ClientId, Room]), + ?LOG_INFO("~p:Room/get:~p", [ClientId, Room]), {reply, {bert, case kvs:get('Room', Room) of {ok, #'Room'{} = R} -> case roster:muc_member(ClientId, Room) of @@ -351,7 +369,7 @@ info(#'Room'{status = get, id = Room}, Req, #cx{params = ClientId} = State) -> info(#'Room'{status = Mute, id = Room}, Req, #cx{params = ClientId, client_pid = C} = State) when Mute == mute;Mute == unmute -> - roster:info(?MODULE, "~p:Room/~p:~p", [ClientId, Mute, Room]), + ?LOG_INFO("~p:Room/~p:~p", [ClientId, Mute, Room]), Mute2 = case Mute of mute -> Mute;_ -> [] end, {reply, {bert, case roster:muc_member(ClientId, Room) of #'Member'{phone_id = PhoneId} -> @@ -366,15 +384,15 @@ info(#'Room'{status = Mute, id = Room}, Req, #cx{params = ClientId, client_pid = _ -> #io{code = #error{code = not_member}} end}, Req, State}; info(#'Room'{} = Room, Req, #cx{params = ClientId} = State) -> - roster:info(?MODULE, "~p:Room/unknown:~p", [ClientId, Room]), + ?LOG_INFO("~p:Room/unknown:~p", [ClientId, Room]), {reply, {bert, #io{code = #error{code = invalid_data}}}, Req, State}; info(#'Member'{} = Member, Req, #cx{params = ClientId} = State) -> - roster:info(?MODULE, "~p:Member/unknown:~p", [ClientId, Member]), + ?LOG_INFO("~p:Member/unknown:~p", [ClientId, Member]), {reply, {bert, #io{code = #error{code = invalid_data}}}, Req, State}. proc(init, #handler{name = roster_room} = Async) -> - roster:info(?MODULE, "ASYNC ROOM PROC started", []), + ?LOG_INFO("ASYNC ROOM PROC started", []), {ok, Async}; %% TODO use roster_push instead proc({send_push, NewMembers, Msg, Type}, #handler{} = H) -> @@ -428,4 +446,4 @@ proc({send_push, NewMembers, Msg, Type}, #handler{} = H) -> end, roster_push:send_push_notification(Ses, Payload, PushAlert, PushType) end || {MemPhoneId, _} = Mem <- ToRoomMembers, Ses <- kvs:index('Auth', user_id, MemPhoneId) - ], {reply, [], H}. \ No newline at end of file + ], {reply, [], H}. diff --git a/apps/roster/src/protocol/roster_roster.erl b/apps/roster/src/protocol/roster_roster.erl index 6afa73b09d499d5dc9d22d8721d65c650a071659..01542644ed0b220e480416f0fc6770ca4fea205b 100644 --- a/apps/roster/src/protocol/roster_roster.erl +++ b/apps/roster/src/protocol/roster_roster.erl @@ -1,4 +1,5 @@ -module(roster_roster). +-include_lib("kernel/include/logger.hrl"). -include("roster.hrl"). -include_lib("n2o/include/n2o.hrl"). -compile(export_all). @@ -14,7 +15,7 @@ info(#'Roster'{status = patch, avatar = Avatar} = Data, Req, state = {verified, #'Roster'{id = RosterId0, phone = Phone, avatar = OldAvatar, roomlist = Rooms, status = Status} = Roster}} = State) -> RosterId = case ClientId of <<"sys_", _/binary>> -> RosterId0; _ -> roster:roster_id(ClientId) end, - roster:info(?MODULE, "~p:~p:Roster/patch:~s", [RosterId, ClientId, io_lib:format("~p", [Data])]), + ?LOG_INFO("~p:~p:Roster/patch:~s", [RosterId, ClientId, io_lib:format("~p", [Data])]), IO = case PreUpdRoster = roster:patch_roster(Roster, Data) of #error{code = invalid_data} = Err -> #io{code = Err}; _ -> @@ -70,13 +71,13 @@ info(#'Roster'{id = RosterId, nick = NickToBeValidated, status = nick} = Data, R ActedRosterId = case ClientId of <<"sys_", _/binary>> -> RosterId; _ -> roster:roster_id(ClientId) end, - roster:info(?MODULE, "~p:~p:Roster/nick.Request:~p", [ClientId, ActedRosterId, Data]), + ?LOG_INFO("~p:~p:Roster/nick.Request:~p", [ClientId, ActedRosterId, Data]), R = case kvs:get('Roster', ActedRosterId) of {error, Reason} -> #io{code = #error{code = Reason}}; {ok, Roster = #'Roster'{phone = Phone, nick = OldNick, status = Status}} -> case regexp_api:validate_string(NickToBeValidated) of {error, NickToBeValidated} -> - roster:error(?MODULE, "Debug.GotInvalidNickFormatError: ~p", [NickToBeValidated]), + ?LOG_ERROR("Debug.GotInvalidNickFormatError: ~p", [NickToBeValidated]), #io{code = #error{code = invalid_nick}}; {ok, Nick} -> % OldNickLow = OldNick, @@ -110,16 +111,16 @@ info(#'Roster'{id = RosterId, nick = NickToBeValidated, status = nick} = Data, R n2o_async:send(system, ?MODULE, {update_ac, UpdNewC}), <<>>; true -> - roster:info(?MODULE, "Debug.GotNickAlreadyUsedError", []), + ?LOG_INFO("Debug.GotNickAlreadyUsedError", []), #io{code = #error{code = nick}} end end end, - roster:info(?MODULE, "~p:~p:Roster/nick.Response:~p", [ClientId, ActedRosterId, R]), + ?LOG_INFO("~p:~p:Roster/nick.Response:~p", [ClientId, ActedRosterId, R]), {reply, {bert, R}, Req, State}; info(#'Roster'{id=RosterId, status=get},Req, #cx{params = ClientId }=State) -> - roster:info(?MODULE, "~p:~p:Roster/get",[RosterId,ClientId]), + ?LOG_INFO("~p:~p:Roster/get",[RosterId,ClientId]), {reply,{bert, case kvs:get('Roster', RosterId) of {ok,#'Roster'{id = Id, phone = Phone, userlist = Contacts}=Roster} -> Roster#'Roster'{userlist = @@ -130,47 +131,47 @@ info(#'Roster'{id=RosterId, status=get},Req, #cx{params = ClientId }=State) -> %%TODO It will be updated in bring with multiaccounts %info(#'Roster'{phone=Phone, id=RosterId, status=remove},Req, % #cx{params = ClientId }=State) -> -% roster:info(?MODULE, "~p:~p:Roster/remove:~p",[Phone,ClientId,RosterId]), +% ?LOG_INFO("~p:~p:Roster/remove:~p",[Phone,ClientId,RosterId]), % Result = roster:remove_roster(Phone,RosterId), % {reply, {bert, {io,Result,<<>>}}, Req, State}; %% TODO delete it later as excessive method %% info(#'Roster'{phone = Phone, status = create}, Req, #cx{params = ClientId} = State) -> -%% roster:info(?MODULE, "~p:~p:Roster/create", [Phone, ClientId]), +%% ?LOG_INFO("~p:~p:Roster/create", [Phone, ClientId]), %% {roster, X} = roster:add_roster(Phone), %% {ok, Roster} = kvs:get('Roster', X), %% {reply, {bert, Roster}, Req, State}; -info(#'Roster'{id = RosterId, userlist = List, status = del}, Req, #cx{params = ClientId, session = Token} = State) -> - roster:info(?MODULE, "~p:~p:Roster/del:~p", [RosterId, ClientId, length(List)]), +info(#'Roster'{id = RosterId, userlist = List, status = del}, Req, #cx{params = ClientId} = State) -> + ?LOG_INFO("~p:~p:Roster/del:~p", [RosterId, ClientId, length(List)]), {reply, {bert, #io{code = case roster:remove_contacts(RosterId, List, ClientId) of #ok2{} = O -> O; E -> E end}}, Req, State}; -info(#'Roster'{id = RosterId, userlist = RosterList, status = add}, Req, #cx{params = ClientId, session = Token} = State) -> - roster:info(?MODULE, "~p:~p:Roster/add:~p", [RosterId, ClientId, length(RosterList)]), +info(#'Roster'{id = RosterId, userlist = RosterList, status = add}, Req, #cx{params = ClientId} = State) -> + ?LOG_INFO("~p:~p:Roster/add:~p", [RosterId, ClientId, length(RosterList)]), List = roster:to_contact(RosterList, request), {reply, {bert, #io{code = roster:add_contacts(RosterId, List)}}, Req, State}; info(#'Roster'{phone = Phone, status = list}, Req, #cx{params = ClientId} = State) -> - roster:info(?MODULE, "~p:~p:Roster/list", [Phone, ClientId]), + ?LOG_INFO("~p:~p:Roster/list", [Phone, ClientId]), {reply, {bert, {io, roster:list_rosters(Phone), <<>>}}, Req, State}; info(#'Roster'{id = RosterId, status = Status}, Req, #cx{params = ClientId} = State) -> - roster:info(?MODULE, "~p:~p:Roster/unknown:~p", [RosterId, ClientId, Status]), + ?LOG_INFO("~p:~p:Roster/unknown:~p", [RosterId, ClientId, Status]), {reply, {bert, #io{code = #error{code = invalid_data}}}, Req, State}. proc(init, #handler{name = ?MODULE} = Async) -> - roster:info(?MODULE, "ASYNC", []), + ?LOG_INFO("ASYNC", []), {ok, Async}; proc({update_ac, #'Contact'{phone_id = PhoneId} = C}, #handler{} = H) -> - [Phone, RosterId] = roster:parts_phone_id(PhoneId), - [begin [P, FriendId] = roster:parts_phone_id(PId), - Res = case roster:get_contact(FriendId, PhoneId) of - #'Contact'{reader = R, update = UPT} -> - roster:update_contact(FriendId, C#'Contact'{reader = R, presence = [], update = UPT}); - E -> E end end - || #'Contact'{phone_id = PId} <- roster:get_on_status(RosterId, friend)], + [_Phone, RosterId] = roster:parts_phone_id(PhoneId), + [begin [_P, FriendId] = roster:parts_phone_id(PId), + case roster:get_contact(FriendId, PhoneId) of + #'Contact'{reader = R, update = UPT} -> + roster:update_contact(FriendId, C#'Contact'{reader = R, presence = [], update = UPT}); + E -> E end end + || #'Contact'{phone_id = PId} <- roster:get_on_status(RosterId, friend)], {reply, [], H}; proc({update_index, #'Roster'{id = RosterId, nick = Nick}}, #handler{} = H) -> @@ -179,6 +180,6 @@ proc({update_index, #'Roster'{id = RosterId, nick = Nick}}, #handler{} = H) -> {reply, [], H}; %% TODO refactor me -proc({delete_index, #'Roster'{id = RosterId, nick = Nick}}, #handler{} = H) -> +proc({delete_index, #'Roster'{nick = Nick}}, #handler{} = H) -> kvs:delete('Index', {nick, Nick}), - {reply, [], H}. \ No newline at end of file + {reply, [], H}. diff --git a/apps/roster/src/protocol/roster_search.erl b/apps/roster/src/protocol/roster_search.erl index 3e2c55c49326f20e37f90d493d5e0031b0d34af6..9ef5658037cf995200cb6d0f34f431377fede8d2 100644 --- a/apps/roster/src/protocol/roster_search.erl +++ b/apps/roster/src/protocol/roster_search.erl @@ -1,4 +1,5 @@ -module(roster_search). +-include_lib("kernel/include/logger.hrl"). -include("roster.hrl"). -include_lib("n2o/include/n2o.hrl"). -compile(export_all). @@ -7,26 +8,26 @@ info(#'Search'{id = FromRosterId, ref = Ref, field = <<"link">>, type = '==', value = SearchVal, status = room}, Req, #cx{params = <<"emqttd_",_/binary>> = ClientId} = State) -> - roster:info(?MODULE, "~p:~p:Search/RoomByLink:~p", [FromRosterId, ClientId, SearchVal]), + ?LOG_INFO("~p:~p:Search/RoomByLink:~p", [FromRosterId, ClientId, SearchVal]), ResRoom = case roster_channel_helper:get_channel_by_link(SearchVal) of [] -> []; R -> [R] end, Res = #'Roster'{id = FromRosterId, roomlist = ResRoom, status = search}, - roster:info(?MODULE, "Res.Search/RoomByLink:~p", [Res]), + ?LOG_INFO("Res.Search/RoomByLink:~p", [Res]), {reply, {bert, {io, {ok, Ref}, Res}}, Req, State}; info(#'Search'{id = FromRosterId, ref = Ref, field = <<"phone_id">>, type = '==', value = [UUID], status = contact}, Req, #cx{params = <<"emqttd_",_/binary>> = ClientId} = State) -> - roster:info(?MODULE, "~p:~p:Search/UUIDk:~p", [FromRosterId, ClientId, UUID]), + ?LOG_INFO("~p:~p:Search/UUIDk:~p", [FromRosterId, ClientId, UUID]), Res = #'Contact'{phone_id = micro:uuid_to_id('LinkRoster', UUID)}, - roster:info(?MODULE, "Res.Search/UUID:~p", [Res]), + ?LOG_INFO("Res.Search/UUID:~p", [Res]), {reply, {bert, {io, {ok, Ref}, Res}}, Req, State}; info(#'Search'{id = From, ref = Ref, field = <<"nick">>, type = '==', value = Q, status = contact}, Req, #cx{params = <<"emqttd_", _/binary>> = ClientId} = State) when Ref /=[] -> - roster:info(?MODULE, "~p:~p:Search/contact:~p", [From, ClientId, Q]), + ?LOG_INFO("~p:~p:Search/contact:~p", [From, ClientId, Q]), PhoneId = case ClientId of % <<"sys_bpe">> -> From0; <<"emqttd_", _/binary>> -> roster:phone_id(ClientId); @@ -41,12 +42,12 @@ info(#'Search'{id = From, ref = Ref, field = <<"nick">>, type = '==', value = Q, Cont#'Contact'{reader = []}; _ -> [] end end || RosterId <- Rs] end || V <- Q] end, Res = #'Roster'{id = From, userlist = lists:flatten(L), status = contact}, - roster:info(?MODULE, "Res.Search/contact:~p", [Res]), + ?LOG_INFO("Res.Search/contact:~p", [Res]), {reply, {bert, {io, {ok, Ref}, Res}}, Req, State}; info(#'Search'{id = From, ref = Ref, field = <<"phone">>, type = '==', value = Phones, status = contact}, Req, #cx{params = <<"emqttd_", _/binary>> = ClientId} = State) when is_list(Phones), Ref /=[] -> - %roster:info(?MODULE, "~p:~p:Search/phone:~p", [From, ClientId, Phones]), + %?LOG_INFO("~p:~p:Search/phone:~p", [From, ClientId, Phones]), PhoneId = case ClientId of % <<"sys_bpe">> -> From0; <<"emqttd_", _/binary>> -> roster:phone_id(ClientId); @@ -62,25 +63,25 @@ info(#'Search'{id = From, ref = Ref, field = <<"phone">>, type = '==', value = P end end || Phone <- lists:usort(Phones), #'Roster'{id=FriendId, phone=_ToPhone}=R <- kvs:index('Roster',phone, Phone)] end, Res = #'Roster'{id = From, userlist = lists:flatten(Result), status = contact}, - roster:info(?MODULE, ":~P", [Res, 30]), - %roster:info(?MODULE, "Res.Search/phone:~p", [Res]), + ?LOG_INFO(":~P", [Res, 30]), + %?LOG_INFO("Res.Search/phone:~p", [Res]), {reply, {bert, {io, {ok, Ref}, Res}}, Req, State}; %% TBD is this method neccesery? info(#'Search'{id = From, ref = Ref, field = K, type = 'like', value = V, status = roster}, Req, #cx{params = <<"emqttd_", _/binary>> = ClientId} = State) when is_atom(K), Ref /=[] -> - roster:info(?MODULE, "~p:~p:Search/names:~p", [From, ClientId, V]), + ?LOG_INFO("~p:~p:Search/names:~p", [From, ClientId, V]), L = [binary_to_list(string:lowercase(V)) ++ F || F <- ets:match(K, {binary_to_list(string:lowercase(V)) ++ '$1'})], {reply, {bert, {io, {ok, Ref}, L}}, Req, State}; info(#'Search'{} = Data, Req, State) -> - roster:info(?MODULE, "Search/unknown:~p", [Data]), + ?LOG_INFO("Search/unknown:~p", [Data]), {reply, {bert, {io, {error,invalid_data}}}, Req, State}. %% TODO search by prefix %%proc(init, #handler{name = roster_search} = Async) -> -%% roster:info(?MODULE, "Search ASYNC started", []), +%% ?LOG_INFO("Search ASYNC started", []), %% case mnesia:wait_for_tables(['Index'], 1000000) of %% ok -> index('Index', nick); %% E -> E diff --git a/apps/roster/src/rest/admin_whitelist.erl b/apps/roster/src/rest/admin_whitelist.erl deleted file mode 100644 index 6df46ca442acfb29bf77175cf03925aba1617d9a..0000000000000000000000000000000000000000 --- a/apps/roster/src/rest/admin_whitelist.erl +++ /dev/null @@ -1,10 +0,0 @@ --module(admin_whitelist). --include("roster.hrl"). --include_lib("roster/include/static/rest_var.hrl"). --compile({parse_transform, lager_transform}). --export([handle_request/3]). - -handle_request('GET', _, Req) -> - roster:info(?MODULE, "~p:GET:~p", [Req:get(path), Req:parse_qs()]), - {ok, HTMLOutput} = phone_numbers_tpl:render([{title, <<"Whitelist">>}]), - Req:respond({?HTTP_CODE_200, [{"Content-Type", "text/html"}], HTMLOutput}). \ No newline at end of file diff --git a/apps/roster/src/rest/helpers/rest_auth_helper.erl b/apps/roster/src/rest/helpers/rest_auth_helper.erl deleted file mode 100644 index deb56d0e4a1750c997304456a7166f15c9674689..0000000000000000000000000000000000000000 --- a/apps/roster/src/rest/helpers/rest_auth_helper.erl +++ /dev/null @@ -1,62 +0,0 @@ --module(rest_auth_helper). --include("roster.hrl"). --include_lib("kvs/include/metainfo.hrl"). - --export([ - description/0, - authorized/1 -]). - --define(AUTH_USERNAME, proplists:get_value(username, application:get_env(rest, basic_auth, []))). --define(AUTH_PASSWORD, proplists:get_value(password, application:get_env(rest, basic_auth, []))). - -description() -> "Rest Basic Authorization Helper". - -authorized(Req) -> - [[], RootPathElem | _] = string:split(Req:get(path), "/", all), - case RootPathElem of - "api" -> %% If the root path of url is 'api' look for Bearer Authentication - case Req:get_header_value("Authorization") of - "Bearer " ++ BearerAuth -> - try - [Token, CId] = string:split(BearerAuth, "/"), - AuthToken = list_to_binary(Token), - ClientId = list_to_binary(CId), - case micro_auth:validate_token(AuthToken) of %% Check if AuthToken is JWT - {ok, UUID} -> - PhoneId = micro:uuid_to_id('LinkRoster', UUID), - case kvs:get('Auth', ClientId) of - {ok, #'Auth'{user_id = PhoneId}} -> {true, api}; - _ -> {error, invalid_token} - end; - error -> %% Seems like token is not JWT - case roster:parse_token(AuthToken) of %% Try using the old Auth - {error, _} = Err -> Err; - _ -> - case kvs:get('Auth', ClientId) of - {ok, #'Auth'{token = DBToken}} when DBToken == AuthToken -> {true, api}; - {error, not_found} -> {error, invalid_token} - end - end - end - catch - _:_ -> {error, invalid_token} - end; - _ -> {error, invalid_token} - end; - _ -> %% If the root path is not 'api' look for Basic Authentication - case Req:get_header_value("Authorization") of - "Basic " ++ BasicAuth -> - case rest_main_helper:is_base64(BasicAuth) of - true -> - case list_to_tuple(binary:split(base64:decode(BasicAuth), <<":">>)) of - {Username, Password} -> - case (nitro:to_binary(Username) == nitro:to_binary(?AUTH_USERNAME) andalso nitro:to_binary(Password) == nitro:to_binary(?AUTH_PASSWORD)) of - true -> true; - _ -> - roster:info(?MODULE, "BasicAuthFailure:Username=~s,Password=~p", [Username, Password]), - false end; - _ -> false end; - _ -> false end; - _ -> false end - end. \ No newline at end of file diff --git a/apps/roster/src/rest/helpers/rest_gw.erl b/apps/roster/src/rest/helpers/rest_gw.erl index 1be6a77f2b17de192a7697cfe318bb2356d1b74f..466a59c3e9036a183b9436237c3e07ae5ae4bb3f 100644 --- a/apps/roster/src/rest/helpers/rest_gw.erl +++ b/apps/roster/src/rest/helpers/rest_gw.erl @@ -1,8 +1,5 @@ -module(rest_gw). -include("roster.hrl"). --include_lib("n2o/include/n2o.hrl"). --include_lib("roster/include/static/rest_var.hrl"). --include_lib("kvs/include/metainfo.hrl"). -compile(export_all). add_as_friend(SenderType, SenderPhoneId, ReceiverPhoneId) -> @@ -48,4 +45,4 @@ send_p2p_message(SenderPhoneId, PhoneId, Text) -> case rest_main_helper:send_to_mqtt(sync, Message) of {reply, {bert, <<>>}, _, _} -> ok; _ -> {error, send_message} - end. \ No newline at end of file + end. diff --git a/apps/roster/src/rest/helpers/rest_log_helper.erl b/apps/roster/src/rest/helpers/rest_log_helper.erl deleted file mode 100644 index 357b8414e8a8b86fa4d77dfc83bf246c76568fe6..0000000000000000000000000000000000000000 --- a/apps/roster/src/rest/helpers/rest_log_helper.erl +++ /dev/null @@ -1,10 +0,0 @@ --module(rest_log_helper). --export([ - checker_error_logs/3, - checker_error_logs/4 -]). - -checker_error_logs(Req, Error, Status) -> - checker_error_logs(Req, Error, Status, ?MODULE). -checker_error_logs(Req, Error, Status, ModuleName) -> - roster:info(ModuleName, "~p:~p:~p:~p(Query:~p;Body:~p)", [Req:get(method), Req:get(path), Status, Error, Req:parse_qs(), mochiweb_request:recv_body(Req)]). diff --git a/apps/roster/src/rest/helpers/rest_main_helper.erl b/apps/roster/src/rest/helpers/rest_main_helper.erl index 33ba9258ab3ce801da4b13ee512826a2347b1f47..94b4b5570a3d5a3b28ccc4fee1176507949e81c2 100644 --- a/apps/roster/src/rest/helpers/rest_main_helper.erl +++ b/apps/roster/src/rest/helpers/rest_main_helper.erl @@ -1,8 +1,8 @@ -module(rest_main_helper). +-include_lib("kernel/include/logger.hrl"). -include("rest_static.hrl"). -include("roster.hrl"). -include_lib("roster/include/static/rest_text.hrl"). --include_lib("roster/include/static/rest_var.hrl"). -include_lib("n2o/include/n2o.hrl"). -export([ description/0, @@ -101,7 +101,7 @@ validate_msg_to_publish(Data) -> try binary_to_list(base64:decode(Data)) catch E:R -> - roster:info(?MODULE, "~p:~pBase64 Decoding Error! ~p", [E, R, Data]), + ?LOG_INFO("~p:~pBase64 Decoding Error! ~p", [E, R, Data]), false end. @@ -110,7 +110,7 @@ is_base64(Data) -> base64:decode(Data), true catch E:R -> - roster:info(?MODULE, "~p:~pBase64DecodingError! ~p", [E, R, Data]), + ?LOG_INFO("~p:~pBase64DecodingError! ~p", [E, R, Data]), false end. @@ -141,4 +141,4 @@ get_client_phone_id(Req) -> end; _ -> throw(authentication_error) - end. \ No newline at end of file + end. diff --git a/apps/roster/src/rest/helpers/rest_response_helper.erl b/apps/roster/src/rest/helpers/rest_response_helper.erl index 20a3365e32df238a176971b293c542ae210562ab..f0019431968a559140dbe88aca16d3414907ac1c 100644 --- a/apps/roster/src/rest/helpers/rest_response_helper.erl +++ b/apps/roster/src/rest/helpers/rest_response_helper.erl @@ -1,6 +1,6 @@ -module(rest_response_helper). +-include_lib("kernel/include/logger.hrl"). -include_lib("roster/include/static/rest_text.hrl"). --include_lib("roster/include/static/rest_var.hrl"). -export([ description/0, @@ -12,6 +12,13 @@ success_200/0 ]). +-define(HTTP_CODE_200, 200). +-define(HTTP_CODE_400, 400). +-define(HTTP_CODE_401, 401). +-define(HTTP_CODE_403, 403). +-define(HTTP_CODE_404, 404). +-define(HTTP_CODE_405, 405). + description() -> "Rest Responses Helper". response(Req, Status, Body) -> @@ -58,16 +65,16 @@ error_response_api(Req, Status, Message) -> 'POST' -> Req:parse_post(); _ -> [] end, - roster:info(?MODULE, "NotFound:~p:~p:~p, Error: ~p", [Req:get(method), Req:get(path), Params, Message]), + ?LOG_INFO("NotFound:~p:~p:~p, Error: ~p", [Req:get(method), Req:get(path), Params, Message]), Body = remove_symbol_screening(jsx:encode([{<<"message">>, nitro:to_binary(Message)}])), Req:respond({Status, [{"Content-Type", "application/json"}], Body}). error_400_response(Req) -> - roster:info(?MODULE, "BadRequest:~p:~p:~p", [Req:get(method), Req:get(path), Req:parse_post()]), + ?LOG_INFO("BadRequest:~p:~p:~p", [Req:get(method), Req:get(path), Req:parse_post()]), response(Req, ?HTTP_CODE_400, rest_response_helper:error_400()). error_405_response(Req) -> - roster:info(?MODULE, "MethodNotAllowed:~p:~p", [Req:get(method), Req:get(path)]), + ?LOG_INFO("MethodNotAllowed:~p:~p", [Req:get(method), Req:get(path)]), response(Req, ?HTTP_CODE_405, rest_response_helper:error_405()). %% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -108,4 +115,4 @@ remove_symbol_screening(Data) -> "\"}{\"", "\"},{\"", [{return, list}, global]), "\"}]\"", "\"}]", [{return, list}, global]), "]\"}}", "]}}", [{return, list}, global]) - ). \ No newline at end of file + ). diff --git a/apps/roster/src/rest/rest_chat_csv.erl b/apps/roster/src/rest/rest_chat_csv.erl index c49a7ac09a7cc3816aa76da72b237ded6efaac2e..05f138a13b01e538771ffd6ab0f0b4490c948514 100644 --- a/apps/roster/src/rest/rest_chat_csv.erl +++ b/apps/roster/src/rest/rest_chat_csv.erl @@ -9,7 +9,6 @@ %%%----------------------------------------------------------------------------- -module(rest_chat_csv). -include("roster.hrl"). --include_lib("roster/include/static/rest_var.hrl"). -include_lib("kvs/include/metainfo.hrl"). -define(MIN_DATE, 0). @@ -21,23 +20,22 @@ -define(EVERYONE, [-1]). -export([ - handle_request/3 + get_user_chat_csv/3, + get_room_chat_csv/3 ]). %% Custom data types -type type_chat_element() :: {MsgDate::integer(), Username::string(), Message::string()}. -type type_chat() :: [type_chat_element()]. --type type_chat_history() :: list(#'Message'{}). -type type_user_data() :: {binary(), string()}. %%%============================================================================= %%% API %%%============================================================================= -handle_request('GET', #{user := ToUserId}, Req) -> - {FromDate, ToDate, TimeZone, AllowedMessageTypes} = parse_get(Req:parse_qs()), + +get_user_chat_csv(QSList, FromPhoneId, ToPhoneId) -> + {FromDate, ToDate, TimeZone, AllowedMessageTypes} = parse_get(QSList), try - ToPhoneId = list_to_binary(ToUserId), - FromPhoneId = rest_main_helper:get_client_phone_id(Req), ChatHistory = get_chat_history({p2p, FromPhoneId, ToPhoneId}), FromUsername = get_username_by_id(extract_user_id(FromPhoneId)), ToUsername = get_username_by_id(extract_user_id(ToPhoneId)), @@ -45,65 +43,51 @@ handle_request('GET', #{user := ToUserId}, Req) -> ChatData = extract_chat_data(ChatHistory, Members, FromDate, ToDate, AllowedMessageTypes, FromPhoneId), CsvText = conversation_to_csv(ChatData, ToUsername, FromDate, ToDate, TimeZone, AllowedMessageTypes), Filename = ["NynjaPrivateChat-", FromUsername, "-", ToUsername, ?CSV_FILE_EXTENSION], - send_response(CsvText, Filename, Req) + {ok, CsvText, Filename} catch - throw:{no_chat_history, {p2p, User1, User2}} -> - rest_response_helper:error_response_api(Req, ?HTTP_CODE_404, io_lib:format("No chat history for: ~s/~s", [User1, User2])); - throw:{no_chat_history, get_chat_time_span} -> - rest_response_helper:error_response_api(Req, ?HTTP_CODE_404, "No chat history for specified period" ); + throw:{no_chat_history, {p2p, User1, User2}} -> + Msg = io_lib:format("No chat history for: ~s/~s", [User1, User2]), + {error, Msg}; + throw:{no_chat_history, get_chat_time_span} -> + {error, "No chat history for specified period"}; throw:{unknown_timezone, TZ} -> - rest_response_helper:error_response_api(Req, ?HTTP_CODE_404, io_lib:format("Unknown timezone: ~s", [TZ])); + {error, io_lib:format("Unknown timezone: ~s", [TZ])}; _:Err -> - roster:info(?MODULE, "~p:~p -UNEXPECTED_ERROR-: ~p:~p~n", [Req:get(method), Req:get(path), Err, erlang:get_stacktrace()]), - rest_response_helper:error_response_api(Req, ?HTTP_CODE_404, "Unexpected error") - end; + {unexpected_error, Err} + end. -handle_request('GET', #{room := GroupChatId}, Req) -> - {FromDate, ToDate, TimeZone, AllowedMessageTypes} = parse_get(Req:parse_qs()), - RoomId = list_to_binary(GroupChatId), - try +get_room_chat_csv(QSList, MemberId, RoomId) -> + {FromDate, ToDate, TimeZone, AllowedMessageTypes} = parse_get(QSList), + try ChatHistory = get_chat_history({muc, RoomId}), Members = get_group_chat_members(RoomId), GroupChatName = get_group_chat_name(RoomId), - #'Member'{id = MemberId} = roster:muc_member(rest_main_helper:get_client_phone_id(Req), RoomId), ChatData = extract_chat_data(ChatHistory, Members, FromDate, ToDate, AllowedMessageTypes, MemberId), CsvText = conversation_to_csv(ChatData, GroupChatName, FromDate, ToDate, TimeZone, AllowedMessageTypes), - Filename = ["NynjaGroupChat-", GroupChatName, ?CSV_FILE_EXTENSION], - send_response(CsvText, Filename, Req) - catch - throw:{no_chat_history, {muc, RoomId}} -> - rest_response_helper:error_response_api(Req, ?HTTP_CODE_404, io_lib:format("No chat history for: ~s", [RoomId])); - throw:{no_chat_history, get_chat_time_span} -> - rest_response_helper:error_response_api(Req, ?HTTP_CODE_404, "No chat history for specified period" ); + Filename = iolist_to_binary(["NynjaGroupChat-", GroupChatName, ?CSV_FILE_EXTENSION]), + {ok, CsvText, Filename} + catch + throw:{no_chat_history, {muc, RoomId}} -> + {error, io_lib:format("No chat history for: ~s", [RoomId])}; + throw:{no_chat_history, get_chat_time_span} -> + {error, "No chat history for specified period"}; throw:{unknown_timezone, TZ} -> - rest_response_helper:error_response_api(Req, ?HTTP_CODE_404, io_lib:format("Unknown timezone: ~s", [TZ])); + {error, io_lib:format("Unknown timezone: ~s", [TZ])}; _:Err -> - roster:info(?MODULE, "~p:~p -UNEXPECTED_ERROR-: ~p:~p~n", [Req:get(method), Req:get(path), Err, erlang:get_stacktrace()]), - rest_response_helper:error_response_api(Req, ?HTTP_CODE_404, "Unexpected error") - end; - -handle_request(_, _, Req) -> - rest_response_helper:error_response_api(Req, ?HTTP_CODE_405, "Method not allowed"). + {unexpected_error, Err} + end. %%%============================================================================= %%% Internal functions %%%============================================================================= -send_response(CsvText, Filename, Req) -> - {ResponseStatus, ResponseData} = {?HTTP_CODE_200, CsvText}, - ResponseHeader = [{"Content-Type", "text/csv"}, {"Content-Disposition", "attachment; filename=" ++ Filename}], - roster:info(?MODULE, "ResponseData:~p", [ResponseData]), - Req:respond({ResponseStatus, ResponseHeader, ResponseData}). parse_get(ReqParams) -> - ReqParams2 = proplists:delete("saveAsLink", ReqParams), - From = element(1, string:to_integer(proplists:get_value("from", ReqParams2, integer_to_list(?MIN_DATE)))), - To = element(1, string:to_integer(proplists:get_value("to", ReqParams2, integer_to_list(?MAX_DATE)))), - TimeZone = proplists:get_value("timezone", ReqParams2, ?DEFAULT_TIMEZONE), - AllowedMessageTypes = proplists:get_keys(proplists:delete("from", - proplists:delete("to", - proplists:delete("timezone", ReqParams2)))), - {From, To, TimeZone, AllowedMessageTypes}. + From = element(1, string:to_integer(proplists:get_value("from", ReqParams, integer_to_list(?MIN_DATE)))), + To = element(1, string:to_integer(proplists:get_value("to", ReqParams, integer_to_list(?MAX_DATE)))), + TimeZone = proplists:get_value("timezone", ReqParams, ?DEFAULT_TIMEZONE), + AllowedMessageTypes = proplists:get_keys(ReqParams), + {From, To, TimeZone, AllowedMessageTypes -- ["from", "to", "timezone", "saveAsLink"]}. conversation_to_csv(ChatData, ToUsername, FromDate, ToDate, TimeZone, AllowedMessageTypes) -> {Start, End} = get_chat_time_span(ChatData, FromDate, ToDate), @@ -133,24 +117,29 @@ build_message_rows([], _, Rows) -> lists:flatten(Rows). extract_chat_data(ChatHistory, Members, FromDate, ToDate, AllowedMessageTypes, Id) -> %% Id can be PhoneId or MemberId - DesiredMessages = [Msg || + DesiredMessages = [Msg || #'Message'{files = [#'Desc'{mime = PayloadType} | _], - created = Date} = Msg <- ChatHistory, + created = Date} = Msg <- ChatHistory, Date >= FromDate, Date =< ToDate, lists:member(binary_to_list(PayloadType), AllowedMessageTypes) == true], extract_chat_data(lists:reverse(DesiredMessages), Members, Id, []). extract_chat_data([], _, _, Acc) -> Acc; extract_chat_data([H | T], Members, Id, Acc) -> case H of - #'Message'{seenby = NotClearedFor, status = clear} when NotClearedFor =/= [Id] -> Acc; - #'Message'{files = [#'Desc'{mime = MessageType, payload = Message} | _], seenby = DeletedFor, - created = Date, from = PhoneId, type = Type, status = Status} - when Type =/= [sys], Status =/= edit, Status =/= delete -> + #'Message'{seenby = NotClearedFor, status = clear} when NotClearedFor =/= [Id] -> + %% TODO check whether we actually want to check 'not lists:member(Id, NotClearedFor)' + %% Possibly we can get data exported if 2 parties have not been cleared from here + %% In other words if 2 parties are added to the room with same "not below here". + Acc; + #'Message'{files = [#'Desc'{mime = MessageType, payload = Message} | _], + seenby = DeletedFor, + created = Date, from = PhoneId, type = Type, status = Status} + when Type =/= [sys], Status =/= edit, Status =/= delete -> case DeletedFor == ?EVERYONE orelse lists:member(Id, DeletedFor) of true -> extract_chat_data(T, Members, Id, Acc); - false -> + false -> Username = match_member_to_phone_id(Members, PhoneId), - extract_chat_data(T, Members, Id, [{Date, Username, format_msg_type(MessageType), Message} | Acc]) + extract_chat_data(T, Members, Id, [{Date, Username, string:titlecase(MessageType), Message} | Acc]) end; _ -> extract_chat_data(T, Members, Id, Acc) end. @@ -181,10 +170,10 @@ get_group_chat_members(RoomId) -> [] -> throw({no_room, RoomId}); Members -> - [ + [ {PhoneId, io_lib:format("~s ~s", [Name, Surname])} - || #'Member'{names = Name, - surnames = Surname, + || #'Member'{names = Name, + surnames = Surname, phone_id = PhoneId} <- Members] end. @@ -203,16 +192,16 @@ get_chat_history(Feed) -> end. -spec get_chat_time_span(type_chat_element(), integer(), integer()) -> {integer(), integer()}. -get_chat_time_span([], FromDate, ToDate) -> throw({no_chat_history, get_chat_time_span}); +get_chat_time_span([],_FromDate,_ToDate) -> throw({no_chat_history, get_chat_time_span}); get_chat_time_span(Chat, FromDate, ToDate) -> - From = - case FromDate of - ?MIN_DATE -> - [{FirstDate, _, _, _} | _] = Chat, + From = + case FromDate of + ?MIN_DATE -> + [{FirstDate, _, _, _} | _] = Chat, FirstDate; _ -> FromDate end, - To = + To = case ToDate of ?MAX_DATE -> [{LastDate, _, _, _} | _] = lists:reverse(Chat), @@ -221,14 +210,10 @@ get_chat_time_span(Chat, FromDate, ToDate) -> end, {From, To}. --spec format_msg_type(binary()) -> list(binary()). -format_msg_type(<>) -> - io_lib:format("~s~s", [string:uppercase(FirstLetter), Rest]). - -spec format_date(tuple(), string()) -> string(). format_date(Date, Format) -> qdate:to_string(Format, Date). --spec format_timezone(integer(), string()) -> tuple() | integer(). +-spec format_timezone(integer(), string()) -> tuple() | integer(). format_timezone(Timestamp, TimeZone) when is_integer(Timestamp), is_list(TimeZone) -> format_timezone(roster:msToUT(Timestamp), TimeZone); format_timezone({{_, _, _}, {_, _, _}} = Date, TimeZone) -> @@ -236,4 +221,4 @@ format_timezone({{_, _, _}, {_, _, _}} = Date, TimeZone) -> {error, unknown_tz} -> throw({unknown_timezone, TimeZone}); LocalDate -> LocalDate end; -format_timezone(Timestamp, _) -> Timestamp. \ No newline at end of file +format_timezone(Timestamp, _) -> Timestamp. diff --git a/apps/roster/src/rest/rest_cowboy_chat_handler.erl b/apps/roster/src/rest/rest_cowboy_chat_handler.erl new file mode 100644 index 0000000000000000000000000000000000000000..daa4b843b9fc083eb809d68d397be8b1f78911fa --- /dev/null +++ b/apps/roster/src/rest/rest_cowboy_chat_handler.erl @@ -0,0 +1,144 @@ +%%%------------------------------------------------------------------- +%%% @doc Cowboy handler for /chat/:roster_uuid/message endpoint +%%% +%%% @end +%%%------------------------------------------------------------------- +-module(rest_cowboy_chat_handler). +-include_lib("kernel/include/logger.hrl"). + +%% Cowboy callbacks +-export([ allowed_methods/2 + , content_types_provided/2 + , init/2 + , is_authorized/2 + , malformed_request/2 + ]). + +%% Custom callbacks +-export([ to_json/2 + ]). + + +%%%=================================================================== +%%% API +%%%=================================================================== + +init(#{path := Path, method := Method, qs := QS} = Req, + #{endpoint := chat} = State) -> + ?LOG_INFO("~s:~s ~p", [Method, Path, QS]), + {cowboy_rest, Req, State}. + +is_authorized(Req, State) -> + rest_cowboy_handler:is_authorized(Req, State). + +allowed_methods(Req, State) -> + {[<<"GET">>], Req, State}. + +-define(bad_query_string, + <<"Bad query params. Please, add it in format:" + " sender=...,sender_type=...,text=...">>). + +malformed_request(Req, State) -> + try cowboy_req:match_qs([sender, sender_type, text], Req) of + QMap -> + {false, Req, State#{qmap => QMap}} + catch + _:_ -> + Req1 = set_error_resp(<<"error">>, + <<"Missing query parameters">>, Req), + {true, Req1, State} + end. + +content_types_provided(Req, State) -> + {[{<<"application/json">>, to_json} + ], + Req, State}. + +%% TODO: GET as state altering operation is not best practice. +to_json(Req, #{qmap := QMap} = State) -> + ReceiverRosterUUID = cowboy_req:binding(roster_uuid, Req), + try {get_user(ReceiverRosterUUID), get_user(maps:get(sender, QMap))} of + {ReceiverPhoneId, SenderPhoneId} -> + State1 = State#{ sender_phone => SenderPhoneId + , receiver_phone => ReceiverPhoneId + }, + to_json1(Req, State1) + catch + throw:{get_user_error, Reason} -> + Status = <<"error">>, + Resp = rest_response_helper:error_response(Status, Reason), + Req1 = cowboy_req:reply(400, #{}, Resp, Req), + {stop, Req1, State} + end. + + +to_json1(Req, #{ sender_phone := SenderPhoneId + , receiver_phone := ReceiverPhoneId + , qmap := #{ sender_type := SenderType + , text := Text }} = State) -> + Receiver = cowboy_req:binding(roster_uuid, Req), + case rest_gw:add_as_friend(SenderType, SenderPhoneId, ReceiverPhoneId) of + {error, What} -> + {Status, Msg} = add_as_friend_error(What, Receiver), + Resp = error_resp(Status, Msg), + {stop, cowboy_req:reply(400, #{}, Resp, Req), State}; + friend_request -> + Status = <<"ok_request_friend">>, + Msg = <<"Friend request send to: ", Receiver/binary>>, + Resp = ok_resp(Status, Msg), + {Resp, Req, State}; + ok -> + case rest_gw:send_p2p_message(SenderPhoneId, ReceiverPhoneId, Text) of + ok -> + Status = <<"ok_message_sent">>, + Msg = <<"Message send to: ", Receiver/binary>>, + Resp = ok_resp(Status, Msg), + {Resp, Req, State}; + {error, send_message} -> + Status = <<"error_sending_message">>, + Msg = <<"Error sending Message to: ", Receiver/binary>>, + Resp = error_resp(Status, Msg), + {stop, cowboy_req:reply(400, #{}, Resp, Req), State} + end + end. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== + +ok_resp(Status, Reason) -> + rest_response_helper:success_response(Status, Reason). + +error_resp(Status, Reason) -> + rest_response_helper:error_response(Status, Reason). + +set_error_resp(Status, Reason, Req) -> + Resp = rest_response_helper:error_response(Status, Reason), + cowboy_req:set_resp_body(Resp, Req). + +get_user(RosterUUID) -> + try uuid:is_valid(binary_to_list(RosterUUID)) of + true -> + case micro:uuid_to_id('LinkRoster', RosterUUID) of + [] -> throw({get_user_error, <<"User not found: ", RosterUUID/binary>>}); + RosterId -> roster:phone_id(RosterId) + end; + false -> + throw({get_user_error, <<"User not found: ", RosterUUID/binary>>}) + catch _:_ -> + throw({get_user_error, <<"Bad User UUID: ", RosterUUID/binary>>}) + end. + +add_as_friend_error(request_not_accepted, Receiver) -> + {<<"error_request_not_accepted">>, + <<"Friend request not accepted by: ", Receiver/binary>>}; +add_as_friend_error(send_friend_request, Receiver) -> + {<<"error_send_friend_request">>, + <<"Error Request Friend/request with: ", Receiver/binary>>}; +add_as_friend_error(confirm_friend_request, Receiver) -> + {<<"error_confirm_friend_request">>, + <<"Error Request Friends/confirm with: ", Receiver/binary>>}; +add_as_friend_error(unexpected_error, Receiver) -> + {<<"error_unexpected">>, + <<"Unexpected Error with: ", Receiver/binary>>}. + diff --git a/apps/roster/src/rest/rest_cowboy_cri_handler.erl b/apps/roster/src/rest/rest_cowboy_cri_handler.erl new file mode 100644 index 0000000000000000000000000000000000000000..ef02dfaebac22488ee3c2f69dd3c7db18ad1b434 --- /dev/null +++ b/apps/roster/src/rest/rest_cowboy_cri_handler.erl @@ -0,0 +1,374 @@ +%%%------------------------------------------------------------------- +%%% @doc Cowboy handler for /cri/rooms +%%% /cri/rooms/members +%%% /cri/rooms/type +%%% /cri/bubbles +%%% +%%% @end +%%%------------------------------------------------------------------- +-module(rest_cowboy_cri_handler). + +-include_lib("kernel/include/logger.hrl"). +-include("roster.hrl"). +-include("rest_static.hrl"). +-include_lib("roster/include/static/rest_text.hrl"). +-include_lib("roster/include/static/roster_text.hrl"). +-include_lib("roster/include/static/roster_var.hrl"). +-include_lib("roster/include/static/main_var.hrl"). + +%% Cowboy callbacks +-export([ allowed_methods/2 + , content_types_accepted/2 + , content_types_provided/2 + , delete_resource/2 + , init/2 + , is_authorized/2 + , forbidden/2 + , malformed_request/2 + ]). + +%% Custom callbacks +-export([ bubble_from_json/2 + , new_room_from_json/2 + , new_room_member_from_json/2 + , room_type_to_json/2 + , room_members_to_json/2 + ]). + +-define(cri_room, cri_room). +-define(cri_room_members, cri_room_members). +-define(cri_room_type, cri_room_type). +-define(cri_bubble, cri_bubble). + +%%%=================================================================== +%%% API +%%%=================================================================== + +init(#{path := Path, method := Method, qs := QS} = Req, + #{endpoint := Endpoint} = State) when Endpoint =:= ?cri_room; + Endpoint =:= ?cri_room_members; + Endpoint =:= ?cri_room_type; + Endpoint =:= ?cri_bubble -> + ?LOG_INFO("~s:~s ~p", [Method, Path, QS]), + {cowboy_rest, Req, State}. + +is_authorized(Req, State) -> + case rest_cowboy_handler:is_authorized(Req, State) of + {{false,_} ,_Req1,_State1} = Ret -> + Ret; + {true, Req1, State1} -> + %% Perform rest of initialization started in + %% malformed_request + try + _ = check_profile(State1), + {true, Req1, init_room(State1)} + catch + throw:{init_error, What} -> + Resp = rest_response_helper:error_response(400, What), + {stop, cowboy_req:reply(400, #{}, Resp, Req1), State1} + end + end. + +allowed_methods(Req, #{endpoint := Endpoint} = State) -> + case Endpoint of + ?cri_room -> {[<<"POST">>, <<"DELETE">>], Req, State}; + ?cri_room_members -> {[<<"GET">>, <<"POST">>, <<"DELETE">>], Req, State}; + ?cri_room_type -> {[<<"GET">>], Req, State}; + ?cri_bubble -> {[<<"POST">>], Req, State} + end. + +%%%=================================================================== +%%% Malformed request + +malformed_request(Req, State) -> + try + State1 = init_phone_header(Req, State), + State2 = init_required_qs_params(Req, State1), + {false, Req, State2} + catch + throw:{init_error, What} -> + {true, set_error_resp(What, Req), State} + end. + +init_phone_header(Req, State) -> + case cowboy_req:header(<<"phoneid">>, Req, undefined) of + undefined -> throw({init_error, ?ERROR_MISSING_HEADER}); + PhoneHeader -> State#{phone_id => rest_main_helper:get_phone_number(PhoneHeader)} + end. + +%% NOTE: Exposes information about DB, so it must be handled after authorization +check_profile(#{ phone_id := PhoneId}) -> + case rest_main_helper:get_profile_by_phone(PhoneId) of + {error, _} -> throw({init_error, ""}); %% TODO: Mimicking existing response. + {ok, _} -> ok + end. + +init_required_qs_params(Req, #{endpoint := Endpoint} = State) -> + Method = cowboy_req:method(Req), + Required = required_qs_param(Endpoint, Method), + try cowboy_req:match_qs(Required, Req) of + #{} = QMap -> maps:merge(State, QMap) + catch _:_ -> throw({init_error, ?ERROR_MISSING_PARAM}) + end. + +%% NOTE: Exposes information about DB, so it must be handled after authorization +init_room(State) -> + case maps:get(room_id, State, undefined) of + undefined -> + State; + RoomId -> + case kvs:get('Room', RoomId) of + {ok, #'Room'{status = Status} = Room} when Status /= delete -> + State#{room_object => Room, room_id => RoomId}; + _ -> + throw({init_error, ?ERROR_ROOM_NOT_FOUND}) + end + end. + +%%%=================================================================== +%%% Forbidden + +forbidden(#{method := <<"DELETE">>} = Req, + #{endpoint := Endpoint} = State) when Endpoint =:= ?cri_room_members; + Endpoint =:= ?cri_room -> + case is_call_room(State) andalso is_admin_user(State) of + true -> {false, Req, State}; + false -> {true, set_error_resp(403, ?ERROR_PERMISSION_DENIED, Req), State} + end; +forbidden(Req, State) -> + {false, Req, State}. + +is_call_room(#{room_object := #'Room'{type = ?CALL_ROOM}}) -> + true; +is_call_room(#{}) -> + false. + +is_admin_user(#{ phone_id := PhoneId, room_id := RoomId }) -> + case roster:muc_member(PhoneId, RoomId) of + #'Member'{status = admin} -> true; + _ -> false + end. + +%%%=================================================================== + +content_types_provided(Req, #{endpoint := Endpoint} = State) -> + {[{<<"application/json">>, endpoint_GET_callback(Endpoint)}], + Req, State}. + +endpoint_GET_callback(?cri_room_type) -> room_type_to_json; +endpoint_GET_callback(?cri_room_members) -> room_members_to_json; +endpoint_GET_callback(_) -> should_not_be_used. + +%%%=================================================================== + +content_types_accepted(Req, #{endpoint := Endpoint} = State) -> + {[{<<"application/json">>, endpoint_POST_callback(Endpoint)}], + Req, State}. + +endpoint_POST_callback(?cri_room) -> new_room_from_json; +endpoint_POST_callback(?cri_room_members) -> new_room_member_from_json; +endpoint_POST_callback(?cri_bubble) -> bubble_from_json. + +%%%=================================================================== + +delete_resource(Req, #{endpoint := ?cri_room, room_id := RoomId} = State) -> + rest_main_helper:send_to_mqtt(#'Room'{id = RoomId, + status = ?DELETE_STATUS, + type = ?CALL_ROOM}), + Json = rest_response_helper:success_response(), + {true, cowboy_req:set_resp_body(Json, Req), State}; +delete_resource(Req, #{endpoint := ?cri_room_members, + room_id := RoomId, + phone_id := AdminPhoneId, + phone_ids := PhoneIdsRaw} = State) -> + {ok, AdminMember} = roster:to_member(AdminPhoneId), + PhoneIdList = re:split(PhoneIdsRaw, ","), + Remove = #'Room'{id = RoomId, + status = ?REMOVE_STATUS, + admins = [AdminMember]}, + {ResponseStatus, ResponseData} = rest_cri:request_with_failed_status(PhoneIdList, Remove), + Req1 = cowboy_req:set_resp_body(ResponseData, Req), + case ResponseStatus of + ok -> + {false, Req1, State}; + error -> + {true, Req1, State} + end. + +%%%=================================================================== + +room_type_to_json(Req, #{room_object := Room} = State) -> + #'Room'{type = Type} = Room, + Json = success_json(jsx_tuples_to_json([{?ROOM_TYPE_VALUE, Type}])), + {Json, Req, State}. + +room_members_to_json(Req, State) -> + #{phone_ids := PhoneIdListRaw, room_id := RoomId} = State, + PhoneIdList = re:split(PhoneIdListRaw, ","), + Json = success_json([jsx_tuples_to_json(member_tuples(PhoneId, RoomId)) + || PhoneId <- PhoneIdList]), + {Json, Req, State}. + +member_tuples(PhoneId, RoomId) -> + case roster:muc_member(nitro:to_binary(PhoneId), RoomId) of + #'Member'{} -> + [{<<"phone_id">>, PhoneId}, + {<<"is_member">>, true}]; + [] -> + PhoneNumber = rest_main_helper:get_phone_number(PhoneId), + case kvs:get('Profile', nitro:to_binary(PhoneNumber)) of + {error, _} -> + [{<<"phone_id">>, PhoneId}, + {<<"is_member">>, false}, + {error, ?ERROR_USER_404}]; + _ -> + [{<<"phone_id">>, PhoneId}, + {<<"is_member">>, false}] + end + end. + +%%%=================================================================== + +new_room_from_json(Req, #{phone_id := PhoneID} = State) -> + RoomId = new_room_id(), + {ok, #'Member'{alias = AdminNick, names = AdminName} = AdminMember} = + roster:to_member(nitro:to_binary(PhoneID)), + Create = #'Room'{id = RoomId, + status = ?CREATE_STATUS, + type = ?CALL_ROOM, + name = new_room_name(AdminNick, AdminName), + admins = [AdminMember]}, + rest_main_helper:send_to_mqtt(sync, Create), + Json = success_json(jsx_tuples_to_json([{<<"room_id">>, RoomId}])), + {true, cowboy_req:set_resp_body(Json, Req), State}. + +new_room_id() -> + TS = nitro:to_binary(roster:now_msec()), + NextId = nitro:to_binary(kvs:next_id('Room', 1)), + iolist_to_binary(["conference_", TS, "_", NextId]). + +new_room_name(AdminNick, AdminName) -> + case AdminNick =:= [] of + true -> iolist_to_binary(["Call by @", AdminName]); + false -> iolist_to_binary(["Call by @", AdminNick]) + end. + +%%%=================================================================== + +new_room_member_from_json(Req, #{phone_id := AdminPhoneId} = State) -> + {ok, Body, Req1} = cowboy_req:read_body(Req), + case parse_new_room_members_body(Body) of + {error, What} -> + {false, set_error_resp(What, Req1), State}; + {ok, #{ room_id := RoomId + , join := true}} -> + {ok, JoinMember} = roster:to_member(AdminPhoneId), + JoinReq = #'Room'{id = RoomId, + status = ?JOIN_STATUS, + type = ?CALL_ROOM, + members = [JoinMember]}, + rest_main_helper:send_to_mqtt(JoinReq), + Json = rest_response_helper:success_response(), + {true, cowboy_req:set_resp_body(Json, Req1), State}; + {ok, #{ room_id := RoomId + , join := true + , phone_ids := PhoneIDs}} -> + case roster:muc_member(AdminPhoneId, RoomId) of + #'Member'{status = admin} = AdminMember -> + PhoneIDList = re:split(PhoneIDs, ","), + Room = #'Room'{id = RoomId, status = ?ADD_STATUS, admins = [AdminMember]}, + case rest_cri:request_with_failed_status(PhoneIDList, Room) of + {ok, Json} -> + Req2 = cowboy_req:set_resp_body(Json, Req1), + {true, Req2, State}; + {error, Json} -> + Req2 = cowboy_req:set_resp_body(Json, Req1), + {false, Req2, State} + end; + _ -> + %% Not an admin + Status = 403, + Error = ?ERROR_PERMISSION_DENIED, + Json = rest_response_helper:error_response(Status, Error), + {stop, cowboy_req:reply(403, #{}, Json, Req1), State} + end + end. + +parse_new_room_members_body(Body) -> + %% room_id, join - always required + %% if join == false - phone_ids required + case rest_main_helper:get_body_value(Body, <<"room_id">>) of + {ok, RoomId} -> + case rest_main_helper:get_body_value(Body, <<"join">>) of + {ok, <<"true">>} -> + {ok , #{ room_id => RoomId + , join => true + }}; + {ok, <<"false">>} -> + case rest_main_helper:get_body_value(Body, <<"phone_id">>) of + {ok, PhoneIds} -> + #{ room_id => RoomId + , join => false + , phone_ids => PhoneIds + }; + {error, _} = Err -> + Err + end; + {error, _} = Err -> + Err + end; + {error, _} = Err -> + Err + end. + +%%%=================================================================== + +bubble_from_json(Req, #{phone_id := FromPhoneId} = State) -> + {ok, Body, Req1} = cowboy_req:read_body(Req), + CallBubbleJSON = 'CallBubbleJSONRest':from_json(jsx:decode(Body), #'CallBubbleJSON'{}), + case rest_cri:validate_bubble_data(CallBubbleJSON) of + error -> + Resp = rest_response_helper:error_response(), + {false, cowboy_req:set_resp_body(Resp, Req), State}; + ok -> + case rest_cri:validate_permission(FromPhoneId, CallBubbleJSON) of + error -> + Status = 403, + Error = ?ERROR_PERMISSION_DENIED, + Json = rest_response_helper:error_response(Status, Error), + {stop, cowboy_req:reply(403, #{}, Json, Req1), State}; + ok -> + Msg = rest_cri:create_message(FromPhoneId, CallBubbleJSON), + rest_main_helper:send_to_mqtt(Msg), + MsgBin = binary_to_list(term_to_binary(Msg)), + Resp = rest_response_helper:success_response(MsgBin), + {true, cowboy_req:set_resp_body(Resp, Req1), State} + end + end. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== + +set_error_resp(Status, What, Req) -> + Resp = rest_response_helper:error_response(Status, What), + cowboy_req:set_resp_body(Resp, Req). + +set_error_resp(What, Req) -> + Resp = rest_response_helper:error_response(What), + cowboy_req:set_resp_body(Resp, Req). + +success_json(Data) -> + rest_response_helper:success_response(Data). + +jsx_tuples_to_json(Data) -> + Fun = fun({K, V}) -> {nitro:to_binary(K), nitro:to_binary(V)}end, + jsx:encode(lists:map(Fun, Data)). + +required_qs_param(?cri_room_type , <<"GET">> ) -> [room_id]; +required_qs_param(?cri_room , <<"DELETE">>) -> [room_id]; +required_qs_param(?cri_room_members , <<"GET">> ) -> [room_id, phone_ids]; +required_qs_param(?cri_room_members , <<"DELETE">>) -> [room_id, phone_ids]; +required_qs_param(_Endpoint , _Method ) -> []. + + diff --git a/apps/roster/src/rest/rest_cowboy_csv_handler.erl b/apps/roster/src/rest/rest_cowboy_csv_handler.erl new file mode 100644 index 0000000000000000000000000000000000000000..a21da1b5958243a3855820c79878fbbc1cde28eb --- /dev/null +++ b/apps/roster/src/rest/rest_cowboy_csv_handler.erl @@ -0,0 +1,92 @@ +%%%------------------------------------------------------------------- +%%% @doc Cowboy handler for /api/v1/chat/messages/csv +%%% /api/v1/groups/messages/csv +%%% +%%% @end +%%%------------------------------------------------------------------- +-module(rest_cowboy_csv_handler). + +-include_lib("kernel/include/logger.hrl"). +-include("roster.hrl"). + +%% Cowboy callbacks +-export([ allowed_methods/2 + , content_types_provided/2 + , init/2 + , is_authorized/2 + ]). + +%% Custom callbacks +-export([ chat_to_csv/2 + , group_chat_to_csv/2 + ]). + +-define(groups_csv, groups_csv). +-define(chats_csv, chats_csv). + +%%%=================================================================== +%%% API +%%%=================================================================== + +init(#{path := Path, method := Method, qs := QS} = Req, + #{endpoint := Endpoint} = State) when Endpoint =:= ?groups_csv; + Endpoint =:= ?chats_csv -> + ?LOG_INFO("~s:~s ~p", [Method, Path, QS]), + {cowboy_rest, Req, State}. + +is_authorized(Req, State) -> + rest_cowboy_handler:is_authorized(Req, State). + +allowed_methods(Req, State) -> + {[<<"GET">>], Req, State}. + +%%%=================================================================== + +content_types_provided(Req, #{endpoint := Endpoint} = State) -> + {[{<<"text/csv">>, endpoint_GET_callback(Endpoint)}], + Req, State}. + +endpoint_GET_callback(?groups_csv) -> group_chat_to_csv; +endpoint_GET_callback(?chats_csv) -> chat_to_csv. + +group_chat_to_csv(Req, #{token_phone_id := FromPhoneID} = State) -> + QSList0 = cowboy_req:parse_qs(Req), + RoomId = cowboy_req:binding(room_id, Req), + case roster:muc_member(FromPhoneID, RoomId) of + #'Member'{id = MemberId} -> + %% For now, the old handler code needs the content as strings. + QSList = [{binary_to_list(K), binary_to_list(V)} || {K, V} <- QSList0], + case rest_chat_csv:get_room_chat_csv(QSList, MemberId, RoomId) of + {ok, CSVFile, Filename} -> + Req1 = cowboy_req:set_resp_header(<<"Content-Disposition">>, + iolist_to_binary(["attachment; filename=", Filename]), Req), + {CSVFile, Req1, State}; + {error, Msg} -> + respond_error(iolist_to_binary(Msg), Req, State) + end; + _ -> + respond_error(<<"Not a member">>, Req, State) + end. + +chat_to_csv(Req, #{token_phone_id := FromPhoneID} = State) -> + QSList0 = cowboy_req:parse_qs(Req), + ToPhoneID = cowboy_req:binding(phone_id, Req), + %% For now, the old handler code needs the content as strings. + QSList = [{binary_to_list(K), binary_to_list(V)} || {K, V} <- QSList0], + case rest_chat_csv:get_user_chat_csv(QSList, FromPhoneID, ToPhoneID) of + {ok, CSVFile, Filename} -> + Req1 = cowboy_req:set_resp_header(<<"Content-Disposition">>, + iolist_to_binary(["attachment; filename=", Filename]), Req), + {CSVFile, Req1, State}; + {error, Msg} -> + respond_error(iolist_to_binary(Msg), Req, State) + end. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== + +respond_error(What, Req, State) when is_binary(What) -> + Msg = jsx:encode([{<<"message">>, What}]), + Headers = #{<<"Content-Type">> => <<"application/json">>}, + {stop, cowboy_req:reply(404, Headers, Msg, Req), State}. diff --git a/apps/roster/src/rest/rest_cowboy_fake_numbers_handler.erl b/apps/roster/src/rest/rest_cowboy_fake_numbers_handler.erl new file mode 100644 index 0000000000000000000000000000000000000000..7e3e8591f72707b92e8795be055b94edc8ab1370 --- /dev/null +++ b/apps/roster/src/rest/rest_cowboy_fake_numbers_handler.erl @@ -0,0 +1,128 @@ +%%%------------------------------------------------------------------- +%%% @doc Cowboy handler for /fake_numbers and /fn (alias) endpoints +%%% +%%% @end +%%%------------------------------------------------------------------- +-module(rest_cowboy_fake_numbers_handler). +-include_lib("kernel/include/logger.hrl"). +-include("roster.hrl"). + +%% Cowboy callbacks +-export([ allowed_methods/2 + , content_types_accepted/2 + , content_types_provided/2 + , delete_resource/2 + , init/2 + , is_authorized/2 + , malformed_request/2 + , resource_exists/2 + ]). + +%% Custom callbacks +-export([ to_json/2 + , to_html/2 + , from_json/2 + ]). + + +%%%=================================================================== +%%% API +%%%=================================================================== + +init(#{path := Path, method := Method, qs := QS} = Req, + #{endpoint := Endpoint} = State) when Endpoint =:= fn; + Endpoint =:= fake_numbers -> + ?LOG_INFO("~s:~s ~p", [Method, Path, QS]), + {cowboy_rest, Req, State}. + +resource_exists(Req, State) -> + {application:get_env(roster, fake_numbers_check, false), Req, State}. + +is_authorized(Req, State) -> + rest_cowboy_handler:is_authorized(Req, State). + +allowed_methods(Req, #{endpoint := fake_numbers} = State) -> + {[<<"GET">>], Req, State}; +allowed_methods(Req, #{endpoint := fn} = State) -> + {[<<"GET">>, <<"PUT">>, <<"DELETE">>], Req, State}. + +-define(phone_not_found, <<"Cannot find phones in query params. Please, add " + "it in format: phone=PhoneNumber1,PhoneNumber2">>). +-define(phone_not_found_body, + <<"Empty Body section. Please, add numbers in format: {\"phone\": " + "[PhoneNumber1,PhoneNumber2]}">>). + +malformed_request(#{method := <<"GET">>} = Req, State) -> + {false, Req, State}; +malformed_request(#{method := <<"DELETE">>} = Req, State) -> + QSList = cowboy_req:parse_qs(Req), + case proplists:get_value(<<"phone">>, QSList) of + undefined -> + {true, set_error_resp(?phone_not_found, Req), State}; + Phone -> + {false, Req, State#{phone => Phone}} + end; +malformed_request(#{method := <<"PUT">>} = Req, State) -> + %% Nothing to check here outside the body, which shouldn't be read here. + {false, Req, State}. + +set_error_resp(Reason, Req) -> + Resp = jsx:encode([{<<"Status">>, <<"Error">>}, + {<<"Reason">>, Reason}]), + cowboy_req:set_resp_body(Resp, Req). + +content_types_provided(Req, #{endpoint := fake_numbers} = State) -> + {[{<<"text/html">>, to_html} + ], + Req, State}; +content_types_provided(Req, #{endpoint := fn} = State) -> + {[{<<"application/json">>, to_json} + ], + Req, State}. + +content_types_accepted(Req, State) -> + {[{<<"application/json">>, from_json} + ], + Req, State}. + +delete_resource(Req, #{phone := Phone} = State) -> + lists:foreach(fun(P) -> + kvs:delete('FakeNumbers', P) + end, re:split(Phone, ",")), + Resp = jsx:encode([{<<"Status">>, <<"Success">>}]), + Req1 = cowboy_req:set_resp_body(Resp, Req), + {true, Req1, State}. + +to_html(#{method := <<"GET">>} = Req, #{endpoint := fake_numbers} = State) -> + {ok, HTMLOutput} = phone_numbers_tpl:render([{title, <<"Fake Numbers">>}]), + {HTMLOutput, Req, State}. + +to_json(#{method := <<"GET">>} = Req, #{endpoint := fn} = State) -> + Data = [#{<<"phone">> => binary_to_integer(P), + <<"created">> => list_to_binary(roster:timestamp_to_datetime((T)))} + || #'FakeNumbers'{created = T, phone = P} + <- kvs:all('FakeNumbers')], + Json = jsx:encode(#{<<"Status">> => <<"Success">>, + <<"Data">> => Data}), + {Json, Req, State}. + +from_json(#{method := <<"PUT">>} = Req, #{endpoint := fn} = State) -> + case cowboy_req:read_body(Req) of + {ok, <<>>, Req1} -> + {false, set_error_resp(?phone_not_found_body, Req1), State}; + {ok, Data, Req1} -> + try jsx:decode(Data, [return_maps]) of + #{<<"phone">> := [_|_] = Phones} -> + lists:foreach(fun add_fake_number/1, Phones), + Res = jsx:encode([{<<"Status">>, <<"Success">>}]), + Req2 = cowboy_req:set_resp_body(Res, Req1), + {true, Req2, State}; + #{} -> + {false, set_error_resp(?phone_not_found_body, Req1), State} + catch _:_ -> + {false, set_error_resp(?phone_not_found_body, Req1), State} + end + end. + +add_fake_number(P) -> + roster:add_fake_number(nitro:to_binary(P)). diff --git a/apps/roster/src/rest/rest_cowboy_handler.erl b/apps/roster/src/rest/rest_cowboy_handler.erl new file mode 100644 index 0000000000000000000000000000000000000000..3abd803047ef3af79e73fdbef5cbe40a718a24cb --- /dev/null +++ b/apps/roster/src/rest/rest_cowboy_handler.erl @@ -0,0 +1,176 @@ +%%%------------------------------------------------------------------- +%%% @doc Handle the rest interface using cowboy +%%% +%%% @end +%%%------------------------------------------------------------------- +-module(rest_cowboy_handler). + +-include_lib("kernel/include/logger.hrl"). +-include("roster.hrl"). + + +-export([ start/0 + , compile_template_files/0 + , compile_template_files/1 + , compile_template_files/2 + , is_authorized/2 + ]). +%%%=================================================================== +%%% API +%%%=================================================================== + +start() -> + start(application:get_env(rest, start_cowboy, false)). + +start(false) -> + ?LOG_INFO("Cowboy REST handler NOT started", []); +start(true) -> + ok = compile_template_files(), + Port = application:get_env(rest, port, 8888), + Opts = [ {port, Port} + ], + HostName = '_', + Env = #{env => #{dispatch => cowboy_router:compile([{HostName, routes()}])}}, + {ok, _} = cowboy:start_clear(roster_cowboy_http, Opts, Env), + ?LOG_INFO("Cowboy REST handler started on port: ~p", [Port]), + ok. + +routes() -> + [ {"/assets/[...]", cowboy_static, {dir, static_assets_dir()}} + , {"/chat/:roster_uuid/message", rest_cowboy_chat_handler, basic_state(chat)} + , {"/sessions", rest_cowboy_session_handler, basic_state(sessions)} + , {"/whitelist", rest_cowboy_whitelist_handler, basic_state(whitelist)} + , {"/admin_whitelist", rest_cowboy_whitelist_handler, basic_state(admin_whitelist)} + , {"/fake_numbers", rest_cowboy_fake_numbers_handler, basic_state(fake_numbers)} + , {"/fn", rest_cowboy_fake_numbers_handler, basic_state(fn)} + , {"/metrics", rest_cowboy_metric_handler, basic_state(metrics)} + , {"/push/message", rest_cowboy_push_handler, basic_state(push_message)} + , {"/room", rest_cowboy_room_handler, basic_state(room)} + , {"/publish", rest_cowboy_publish_handler, basic_state(publish)} + , {"/users", rest_cowboy_users_handler, basic_state(users)} + , {"/cri/rooms/type", rest_cowboy_cri_handler, basic_state(cri_room_type)} + , {"/cri/rooms", rest_cowboy_cri_handler, basic_state(cri_room)} + , {"/cri/rooms/members", rest_cowboy_cri_handler, basic_state(cri_room_members)} + , {"/cri/bubbles", rest_cowboy_cri_handler, basic_state(cri_bubble)} + , {"/test/", rest_cowboy_test_handler, basic_state(test_api)} + , {"/link/:link_id/room_id", rest_cowboy_link_handler, basic_state(link_room)} + , {"/api/v1/groups/:room_id/messages/csv", + rest_cowboy_csv_handler, token_state(groups_csv)} + , {"/api/v1/chats/:phone_id/messages/csv", + rest_cowboy_csv_handler, token_state(chats_csv)} + , {"/api/v1/groups/:room_id/messages/:message_id/transcribe", + rest_cowboy_transcribe_handler, token_state(groups_transcribe)} + , {"/api/v1/chats/:phone_id/messages/:message_id/transcribe", + rest_cowboy_transcribe_handler, token_state(chats_transcribe)} + ]. + +token_state(Endpoint) -> + #{ auth => token + , endpoint => Endpoint}. + +basic_state(Endpoint) -> + #{ auth => basic + , endpoint => Endpoint}. + +static_assets_dir() -> + filename:join([code:priv_dir(roster), "www", "assets"]). + +%% Compile template (dtl) files +compile_template_files() -> + compile_template_files([]). + +compile_template_files(Opts) -> + Files = filelib:wildcard(filename:join([code:priv_dir(roster), "www", "*.dtl"])), + compile_template_files(Files, Opts). + +compile_template_files([], _Opts) -> + ok; +compile_template_files([File | Files], Opts) -> + Out = re:replace(filename:basename(File), ".dtl", "_tpl", + [global, {return, list}]), + ok = erlydtl:compile(File, Out, Opts), + compile_template_files(Files, Opts). + + +%%%=================================================================== +%%% Authorization + +is_authorized(#{path := Path} = Req, #{auth := AuthType} = State) -> + case lists:member(Path, application:get_env(rest, open_api_list, [])) of + true -> {true, Req, State}; + false -> + case cowboy_req:header(<<"authorization">>, Req) of + undefined when AuthType =:= basic -> + {{false, <<"Basic">>}, Req, State}; + undefined when AuthType =:= token -> + Req1 = cowboy_req:set_resp_body(<<"Token is invalid">>, Req), + {{false, <<"Bearer">>}, Req1, State}; + AuthHeader when AuthType =:= token -> + token_authentication(AuthHeader, Req, State); + AuthHeader when AuthType =:= basic -> + case basic_auth(AuthHeader) of + true -> {true, Req, State}; + false -> {{false, <<"Basic">>}, Req, State} + end + end + end. + +token_authentication(Auth, Req, State) -> + case token_auth_with_phone_id(Auth) of + {ok, PhoneId} -> + {true, Req, State#{token_phone_id => iolist_to_binary(PhoneId)}}; + {error, invalid_token} -> + Req1 = cowboy_req:set_resp_body(<<"Token is invalid">>, Req), + {{false, <<"Bearer">>}, Req1, State}; + {error, token_expired} -> + Req1 = cowboy_req:set_resp_body(<<"Token is expired">>, Req), + {{false, <<"Bearer">>}, Req1, State} + end. + +-define(AUTH_USERNAME, proplists:get_value(username, application:get_env(rest, basic_auth, []))). +-define(AUTH_PASSWORD, proplists:get_value(password, application:get_env(rest, basic_auth, []))). + +basic_auth(<<"Basic", BasicAuth/binary>>) -> + try binary:split(base64:decode(BasicAuth), <<":">>) of + [Username, Password] -> + (iolist_to_binary(?AUTH_USERNAME) =:= Username) + andalso (iolist_to_binary(?AUTH_PASSWORD) =:= Password); + _ -> false + catch + _:_ -> false + end; +basic_auth(Bin) when is_binary(Bin) -> + false. + +token_auth_with_phone_id(<<"Bearer ", BearerAuth/binary>>) -> + try + [AuthToken, ClientId] = re:split(BearerAuth, "/"), + %% Check if AuthToken is JWT + case micro_auth:validate_token(AuthToken) of + {ok, UUID} -> + PhoneId = micro:uuid_to_id('LinkRoster', UUID), + case kvs:get('Auth', ClientId) of + {ok, #'Auth'{user_id = PhoneId}} -> + {ok, PhoneId}; + _ -> + {error, invalid_token} + end; + error -> + %% Seems like token is not JWT + %% Try using the old Auth + case roster:parse_token(AuthToken) of + {error, _} = Err -> + Err; + _ -> + case kvs:get('Auth', ClientId) of + {ok, #'Auth'{token = DBToken}} when DBToken == AuthToken -> ok; + {error, not_found} -> {error, invalid_token} + end + end + end + catch + _:_ -> {error, invalid_token} + end; +token_auth_with_phone_id(Bin) when is_binary(Bin) -> + {error, invalid_token}. + diff --git a/apps/roster/src/rest/rest_cowboy_link_handler.erl b/apps/roster/src/rest/rest_cowboy_link_handler.erl new file mode 100644 index 0000000000000000000000000000000000000000..82216edc8d41ac287b1d022a932cfaebcaa2699b --- /dev/null +++ b/apps/roster/src/rest/rest_cowboy_link_handler.erl @@ -0,0 +1,66 @@ +%%%------------------------------------------------------------------- +%%% @doc Cowboy handler for /link/:link_id/room_id +%%% +%%% @end +%%%------------------------------------------------------------------- +-module(rest_cowboy_link_handler). + +-include_lib("kernel/include/logger.hrl"). +-include("roster.hrl"). + +%% Cowboy callbacks +-export([ allowed_methods/2 + , content_types_provided/2 + , init/2 + , is_authorized/2 + , resource_exists/2 + ]). + +%% Custom callbacks +-export([ room_to_json/2 + ]). + +%%%=================================================================== +%%% API +%%%=================================================================== + +init(#{path := Path, method := Method, qs := QS} = Req, + #{endpoint := link_room} = State) -> + ?LOG_INFO("~s:~s ~p", [Method, Path, QS]), + {cowboy_rest, Req, State}. + +is_authorized(Req, State) -> + rest_cowboy_handler:is_authorized(Req, State). + +allowed_methods(Req, #{} = State) -> + {[<<"GET">>], Req, State}. + +content_types_provided(Req, State) -> + {[{<<"application/json">>, room_to_json}], + Req, State}. + +resource_exists(Req, State) -> + LinkId = cowboy_req:binding(link_id, Req), + case kvs:get('Link', LinkId) of + {ok, #'Link'{room_id = RoomId}} -> + case kvs:get('Room', RoomId) of + {ok, #'Room'{name = RoomName}} -> + {true, Req, State#{ room_id => RoomId + , room_name => RoomName}}; + _ -> + Json = jsx:encode([ {room_id, list_to_binary(RoomId)} + , {error, <<"not_found">>}]), + Req1 = cowboy_req:set_resp_body(Json, Req), + {false, Req1, State} + end; + {error, not_found} -> + Json = jsx:encode([ {link_id, LinkId} + , {error, <<"not_found">>}]), + Req1 = cowboy_req:set_resp_body(Json, Req), + {false, Req1, State} + end. + +room_to_json(Req, #{room_id := RoomId, room_name := RoomName} = State) -> + Json = jsx:encode([ { room_id, RoomId} + , {room_name, RoomName}]), + {Json, Req, State}. diff --git a/apps/roster/src/rest/rest_cowboy_metric_handler.erl b/apps/roster/src/rest/rest_cowboy_metric_handler.erl new file mode 100644 index 0000000000000000000000000000000000000000..2b88370df1985b9c6604bc8a1d7df3a9784b5fb5 --- /dev/null +++ b/apps/roster/src/rest/rest_cowboy_metric_handler.erl @@ -0,0 +1,56 @@ +%%%------------------------------------------------------------------- +%%% @doc Cowboy handler for /metrics endpoint +%%% +%%% @end +%%%------------------------------------------------------------------- +-module(rest_cowboy_metric_handler). + +-include_lib("kernel/include/logger.hrl"). +-include_lib("roster/include/static/prometheus_var.hrl"). + +%% Cowboy callbacks +-export([ init/2 + , is_authorized/2 + , allowed_methods/2 + , content_types_provided/2 + ]). + +%% Custom callbacks +-export([ to_text/2 + ]). + + +%%%=================================================================== +%%% API +%%%=================================================================== + +init(#{path := Path, method := Method} = Req, #{endpoint := metrics} = State) -> + ?LOG_INFO("~s:~s", [Method, Path]), + {cowboy_rest, Req, State}. + +is_authorized(Req, State) -> + rest_cowboy_handler:is_authorized(Req, State). + +allowed_methods(Req, State) -> + {[<<"GET">>], Req, State}. + +content_types_provided(Req, State) -> + {[{<<"text/plain">>, to_text} + ], + Req, State}. + +to_text(Req, State) -> + update_gauges(), + Resp = prometheus_text_format:format(), + {Resp, Req, State}. + +update_gauges() -> + prometheus_gauge:set(?METRIC_CHATS_TOTAL, [?METRIC_LABEL_P2P_CHAT], roster_db:p2p_stats()), + prometheus_gauge:set(?METRIC_CHATS_TOTAL, [?METRIC_LABEL_GROUP_CHAT], roster_db:room_stats()), + [prometheus_gauge:set(?METRIC_USERS_PER_COUNTRY, [Country], Value) || {Country, Value} <- roster_db:country_user_stats()], + %% [prometheus_gauge:set(?METRIC_USERS_PER_COUNTRY_ONLINE, [Country], Value) || {Country, Value} <- roster_db:country_user_stats(online)], + [prometheus_gauge:set(?METRIC_MSGS_BY_TYPE_NMBR, [Mime], Value) || {Mime, Value} <- roster_db:msg_stats()], + [prometheus_gauge:set(?METRIC_SESSIONS_PER_COUNTRY_ONLINE, [Country], Value) || {Country, Value} <- roster_db:country_session_stats(online)], + ok. + + diff --git a/apps/roster/src/rest/rest_cowboy_publish_handler.erl b/apps/roster/src/rest/rest_cowboy_publish_handler.erl new file mode 100644 index 0000000000000000000000000000000000000000..a030da52bbd817765841f3c4d5ede4d611cba7ff --- /dev/null +++ b/apps/roster/src/rest/rest_cowboy_publish_handler.erl @@ -0,0 +1,65 @@ +%%%------------------------------------------------------------------- +%%% @doc Cowboy handler for /publish +%%% +%%% @end +%%%------------------------------------------------------------------- +-module(rest_cowboy_publish_handler). +-include_lib("kernel/include/logger.hrl"). +-include("roster.hrl"). +-include_lib("roster/include/static/rest_text.hrl"). + +%% Cowboy callbacks +-export([ allowed_methods/2 + , content_types_accepted/2 + , init/2 + , is_authorized/2 + ]). + +%% Custom callbacks +-export([ from_json/2 + ]). + + +%%%=================================================================== +%%% API +%%%=================================================================== + +init(#{path := Path, method := Method, qs := QS} = Req, + #{endpoint := publish} = State) -> + ?LOG_INFO("~s:~s ~p", [Method, Path, QS]), + {cowboy_rest, Req, State}. + +is_authorized(Req, State) -> + rest_cowboy_handler:is_authorized(Req, State). + +allowed_methods(Req, State) -> + {[<<"POST">>], Req, State}. + +content_types_accepted(Req, State) -> + {[{<<"application/json">>, from_json} + ], + Req, State}. + +from_json(Req, State) -> + {ok, Body, Req1} = cowboy_req:read_body(Req), + try + [throw({validation, ?ERROR_INVALID_JSON}) || not jsx:is_json(Body)], + PS = 'PublishServiceRest':from_json(jsx:decode(Body), #'PublishService'{}), + #'PublishService'{message = Msg, topic = Topic, qos = Qos} = PS, + [throw({validation, ?ERROR_MISSING_PARAM}) || Msg == [] orelse Topic == []], + [throw({validation, ?ERROR_INVALID_QOS_PARAM}) || Qos < 0 orelse Qos > 2], + case rest_main_helper:validate_msg_to_publish(Msg) of + false -> + throw({validation, ?ERROR_INVALID_MESSAGE_PARAM}); + DecodedMsg -> + Pid = rest_main_helper:rest_pid(), + MsgBin = term_to_binary(DecodedMsg), + n2o_vnode:send(Pid, Topic, MsgBin, [{qos, Qos}]), + Json = rest_response_helper:success_200(), + Req2 = cowboy_req:set_resp_body(Json, Req1), + {true, Req2, State} + end + catch throw:{validation, What} -> + ErrJson = rest_response_helper:error_response(What), + {false, cowboy_req:set_resp_body(ErrJson, Req), State} + end. diff --git a/apps/roster/src/rest/rest_cowboy_push_handler.erl b/apps/roster/src/rest/rest_cowboy_push_handler.erl new file mode 100644 index 0000000000000000000000000000000000000000..5b603bd61c7ecaf88cc8a8e11c2afe821f6cdaea --- /dev/null +++ b/apps/roster/src/rest/rest_cowboy_push_handler.erl @@ -0,0 +1,64 @@ +%%%------------------------------------------------------------------- +%%% @doc Cowboy handler for /push/message +%%% +%%% @end +%%%------------------------------------------------------------------- +-module(rest_cowboy_push_handler). +-include_lib("kernel/include/logger.hrl"). +-include("roster.hrl"). + +%% Cowboy callbacks +-export([ allowed_methods/2 + , content_types_accepted/2 + , init/2 + , is_authorized/2 + ]). + +%% Custom callbacks +-export([ from_json/2 + ]). + + +%%%=================================================================== +%%% API +%%%=================================================================== + +init(#{path := Path, method := Method, qs := QS} = Req, + #{endpoint := push_message} = State) -> + ?LOG_INFO("~s:~s ~p", [Method, Path, QS]), + {cowboy_rest, Req, State}. + +is_authorized(Req, State) -> + rest_cowboy_handler:is_authorized(Req, State). + +allowed_methods(Req, State) -> + {[<<"POST">>], Req, State}. + +content_types_accepted(Req, State) -> + {[{<<"application/json">>, from_json} + ], + Req, State}. + +from_json(Req, State) -> + {ok, Body, Req1} = cowboy_req:read_body(Req), + case jsx:is_json(Body) of + false -> + ?LOG_INFO("Error Invalid Json", []), + {false, Req1, State}; + true -> + PS = 'PushServiceRest':from_json(jsx:decode(Body), #'PushService'{}), + Payload = list_to_binary(PS#'PushService'.payload), + Recipients = PS#'PushService'.recipients, + PushAlert = PushType = list_to_binary(PS#'PushService'.module), + Pid = n2o_async:pid(system, roster_push), + lists:foreach( + fun(PhoneId0) -> + PhoneId = iolist_to_binary(PhoneId0), + AuthList = kvs:index('Auth', user_id, PhoneId), + lists:foreach( + fun(Auth) -> + Pid ! {async_push, Auth, Payload, PushAlert, PushType} + end, AuthList) + end, Recipients), + {true, Req1, State} + end. diff --git a/apps/roster/src/rest/rest_cowboy_room_handler.erl b/apps/roster/src/rest/rest_cowboy_room_handler.erl new file mode 100644 index 0000000000000000000000000000000000000000..f8fba266c6b0d8103c1fcc836868f825fe8b011e --- /dev/null +++ b/apps/roster/src/rest/rest_cowboy_room_handler.erl @@ -0,0 +1,74 @@ +%%%------------------------------------------------------------------- +%%% @doc Cowboy handler for /room +%%% +%%% @end +%%%------------------------------------------------------------------- +-module(rest_cowboy_room_handler). +-include_lib("kernel/include/logger.hrl"). +-include("roster.hrl"). + +%% Cowboy callbacks +-export([ allowed_methods/2 + , content_types_provided/2 + , init/2 + , is_authorized/2 + , malformed_request/2 + , resource_exists/2 + ]). + +%% Custom callbacks +-export([ to_json/2 + ]). + + +%%%=================================================================== +%%% API +%%%=================================================================== + +init(#{path := Path, method := Method, qs := QS} = Req, + #{endpoint := room} = State) -> + ?LOG_INFO("~s:~s ~p", [Method, Path, QS]), + {cowboy_rest, Req, State}. + +is_authorized(Req, State) -> + rest_cowboy_handler:is_authorized(Req, State). + +allowed_methods(Req, State) -> + {[<<"GET">>], Req, State}. + +malformed_request(Req, State) -> + QSList = cowboy_req:parse_qs(Req), + case proplists:get_value(<<"link">>, QSList) of + undefined -> + Resp = resp_json(<<"Query Parameter Not Found, Check your request">>), + Req1 = cowboy_req:set_resp_body(Resp, Req), + {true, Req1, State}; + Link -> + {false, Req, State#{link => Link}} + end. + +resource_exists(Req, #{link := Link} = State) -> + case roster_channel_helper:get_channel_by_link(Link) of + [] -> + Resp = resp_json(<<"Channel not found">>), + Req1 = cowboy_req:set_resp_body(Resp, Req), + {false, Req1, State}; + Room -> + {true, Req, State#{room => Room}} + end. + +content_types_provided(Req, State) -> + {[{<<"application/json">>, to_json} + ], + Req, State}. + +to_json(Req, #{room := Room} = State) -> + Avatar = roster_channel_helper:get_room_avatar(Room), + Json = resp_json([ {<<"id">>, Room#'Room'.id} + , {<<"name">>, Room#'Room'.name} + , {<<"description">>, Room#'Room'.description} + , {<<"avatar">>, Avatar}]), + {Json, Req, State}. + +resp_json(Data) -> + jsx:encode([{<<"data">>, Data}]). diff --git a/apps/roster/src/rest/rest_cowboy_session_handler.erl b/apps/roster/src/rest/rest_cowboy_session_handler.erl new file mode 100644 index 0000000000000000000000000000000000000000..1d4b27941e3b368404d3bb67a8577f6a5285b081 --- /dev/null +++ b/apps/roster/src/rest/rest_cowboy_session_handler.erl @@ -0,0 +1,108 @@ +%%%------------------------------------------------------------------- +%%% @doc Cowboy handler for /sessions endpoint +%%% +%%% @end +%%%------------------------------------------------------------------- +-module(rest_cowboy_session_handler). + +-include_lib("kernel/include/logger.hrl"). +-include("roster.hrl"). + +%% Cowboy callbacks +-export([ init/2 + , is_authorized/2 + , allowed_methods/2 + , content_types_provided/2 + , malformed_request/2 + , resource_exists/2 + ]). + +%% Custom callbacks +-export([ to_json/2 + ]). + + +%%%=================================================================== +%%% API +%%%=================================================================== + +init(#{path := Path, method := Method, qs := QS} = Req, + #{endpoint := sessions} = State) -> + ?LOG_INFO("~s:~s ~p", [Method, Path, QS]), + {cowboy_rest, Req, State}. + +resource_exists(Req, State) -> + {application:get_env(roster, get_sessions_api, false), Req, State}. + +is_authorized(Req, State) -> + rest_cowboy_handler:is_authorized(Req, State). + +allowed_methods(Req, State) -> + {[<<"GET">>], Req, State}. + + +-define(empty_qs, <<"Empty query parameter. Please, add it in format:" + " phone=RequestPhoneNumber">>). +-define(invalid_qs, <<"Invalid query parameter. Please, add it in format:" + " phone=RequestPhoneNumber">>). +-define(no_sessions, <<"Cannot find sessions for requested number. " + "Please, check input query parameters">>). + +malformed_request(Req, State) -> + case cowboy_req:parse_qs(Req) of + [] -> + {stop, reply_plain(400, ?empty_qs, Req), State}; + QSList -> + case proplists:get_value(<<"phone">>, QSList) of + undefined -> + {stop, reply_plain(400, ?invalid_qs, Req), State}; + Phone -> + {false, Req, State#{phone => Phone}} + end + end. + +content_types_provided(Req, State) -> + {[{<<"application/json">>, to_json} + ], + Req, State}. + +to_json(Req, #{phone := Phone} = State) -> + case kvs:index('Auth', phone, Phone) of + [] -> + {stop, reply_plain(400, ?no_sessions, Req), State}; + Sessions -> + Json = sessions_to_json(Sessions), + {Json, Req, State} + end. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== + +reply_plain(Status, Msg, Req) -> + Headers = #{<<"content-type">> => <<"text/plain">>}, + cowboy_req:reply(Status, Headers, Msg, Req). + +sessions_to_json(Sessions) -> + %% TODO: Flatten this out to not need the replace + Res0 = + [begin + FL0 = [jsx:encode('FeatureRest':to_json(Feature)) + || Feature <- FeaturesList], + FL1 = roster:binary_join(lists:flatten(FL0), <<",">>), + S1 = S#'Auth'{settings = iolist_to_binary([<<"[">>, FL1, <<"]">>])}, + jsx:encode('AuthRest':to_json(S1)) + end + || #'Auth'{settings = FeaturesList} = S <- Sessions], + Res1 = iolist_to_binary( + [<<"[">>, roster:binary_join(lists:flatten(Res0), <<",">>), <<"]">>]), + re:replace( + re:replace( + re:replace( + re:replace( + re:replace( + Res1, "\\\\\\\"", "\\\"", [{return, list}, global]), + "\\]\\\"", "\\]", [{return, list}, global]), + "\\\"\\[", "\\[", [{return, list}, global]), + "\\\"\\{", "\\{", [{return, list}, global]), + "\\}\\\"", "\\}", [{return, list}, global]). diff --git a/apps/roster/src/rest/rest_cowboy_test_handler.erl b/apps/roster/src/rest/rest_cowboy_test_handler.erl new file mode 100644 index 0000000000000000000000000000000000000000..3ffeb38081eff6b1e4773f064afae17e0d7a0bbf --- /dev/null +++ b/apps/roster/src/rest/rest_cowboy_test_handler.erl @@ -0,0 +1,44 @@ +%%%------------------------------------------------------------------- +%%% @doc Cowboy handler for /test endpoint +%%% +%%% @end +%%%------------------------------------------------------------------- +-module(rest_cowboy_test_handler). +-include_lib("kernel/include/logger.hrl"). + +%% Cowboy callbacks +-export([ allowed_methods/2 + , content_types_provided/2 + , init/2 + , is_authorized/2 + ]). + +%% Custom callbacks +-export([ to_json/2 + ]). + +%%%=================================================================== +%%% API +%%%=================================================================== + +init(#{path := Path, method := Method, qs := QS} = Req, + #{endpoint := test_api} = State) -> + ?LOG_INFO("~s:~s ~p", [Method, Path, QS]), + {cowboy_rest, Req, State}. + +is_authorized(Req, State) -> + rest_cowboy_handler:is_authorized(Req, State). + +allowed_methods(Req, State) -> + {[<<"GET">>], Req, State}. + +content_types_provided(Req, State) -> + {[{<<"application/json">>, to_json} + ], + Req, State}. + +%% TODO: GET as state altering operation is not best practice. +to_json(Req, State) -> + TS = roster:timestamp_to_datetime(roster:now_msec()), + JSon = jsx:encode([{<<"Current time">>, list_to_binary(TS)}]), + {JSon, Req, State}. diff --git a/apps/roster/src/rest/rest_cowboy_transcribe_handler.erl b/apps/roster/src/rest/rest_cowboy_transcribe_handler.erl new file mode 100644 index 0000000000000000000000000000000000000000..fc633efeea3c7ca303e99687a74ce5b87753a712 --- /dev/null +++ b/apps/roster/src/rest/rest_cowboy_transcribe_handler.erl @@ -0,0 +1,73 @@ +%%%------------------------------------------------------------------- +%%% @doc Cowboy handler for /api/v1/chat/messages/transcribe +%%% /api/v1/groups/messages/transcribe +%%% +%%% @end +%%%------------------------------------------------------------------- + +%% TODO: This doesn't seem ready at all. Included in migration anyway +%% in the off chance that someone relies on it. + +-module(rest_cowboy_transcribe_handler). +-include_lib("kernel/include/logger.hrl"). + +%% Cowboy callbacks +-export([ allowed_methods/2 + , content_types_provided/2 + , init/2 + , is_authorized/2 + , resource_exists/2 + , malformed_request/2 + ]). + +%% Custom callbacks +-export([ dummy_to_json/2 + ]). + +-define(groups_transcribe, groups_transcribe). +-define(chats_transcribe, chats_transcribe). + +%%%=================================================================== +%%% API +%%%=================================================================== + +init(#{path := Path, method := Method, qs := QS} = Req, + #{endpoint := Endpoint} = State) when Endpoint =:= ?groups_transcribe; + Endpoint =:= ?chats_transcribe -> + ?LOG_INFO("~s:~s ~p", [Method, Path, QS]), + {cowboy_rest, Req, State}. + +is_authorized(Req, State) -> + rest_cowboy_handler:is_authorized(Req, State). + +allowed_methods(Req, #{} = State) -> + {[<<"GET">>], Req, State}. + +malformed_request(Req, State) -> + try cowboy_req:match_qs([lang], Req) of + #{lang := Lang} -> {false, Req, State#{lang => Lang}} + catch + _:_ -> + Json = rest_response_helper:error_400(), + {true, cowboy_req:set_resp_body(Json, Req), State} + end. + +resource_exists(Req, #{endpoint := ?groups_transcribe} = State) -> + _RoomID = cowboy_req:binding(room_id, Req), + MsgId = cowboy_req:binding(message_id, Req), + case kvs:get('Message', MsgId) of + {ok,_Msg} -> {true, Req, State}; + {error, not_found} -> {false, Req, State} + end. + +content_types_provided(Req, #{endpoint := Endpoint} = State) -> + {[{<<"application/json">>, endpoint_GET_callback(Endpoint)}], + Req, State}. + +endpoint_GET_callback(?groups_transcribe) -> dummy_to_json; +endpoint_GET_callback(?chats_transcribe) -> dummy_to_json. + +dummy_to_json(Req, State) -> + Resp = jsx:encode([{<<"transcription">>, + <<"hardcoded transcription of text">>}]), + {Resp, Req, State}. diff --git a/apps/roster/src/rest/rest_cowboy_users_handler.erl b/apps/roster/src/rest/rest_cowboy_users_handler.erl new file mode 100644 index 0000000000000000000000000000000000000000..138620201a2efc02a738e509ceec5e5b00a24720 --- /dev/null +++ b/apps/roster/src/rest/rest_cowboy_users_handler.erl @@ -0,0 +1,80 @@ +%%%------------------------------------------------------------------- +%%% @doc Cowboy handler for /users +%%% +%%% @end +%%%------------------------------------------------------------------- +-module(rest_cowboy_users_handler). + +-include_lib("kernel/include/logger.hrl"). +-include("roster.hrl"). + +%% Cowboy callbacks +-export([ allowed_methods/2 + , content_types_provided/2 + , init/2 + , is_authorized/2 + , malformed_request/2 + ]). + +%% Custom callbacks +-export([ to_json/2 + ]). + + +%%%=================================================================== +%%% API +%%%=================================================================== + +init(#{path := Path, method := Method, qs := QS} = Req, + #{endpoint := users} = State) -> + ?LOG_INFO("~s:~s ~p", [Method, Path, QS]), + {cowboy_rest, Req, State}. + +is_authorized(Req, State) -> + rest_cowboy_handler:is_authorized(Req, State). + +allowed_methods(Req, State) -> + {[<<"GET">>], Req, State}. + +malformed_request(Req, State) -> + try cowboy_req:match_qs([ {order, [], <<"desc">>} + , {page_size, int, 50} + , {page, int, 1} + ], + Req) of + QMap -> + {false, Req, State#{qmap => QMap}} + catch _:_ -> + {true, Req, State} + end. + +content_types_provided(Req, State) -> + {[{<<"application/json">>, to_json} + ], + Req, State}. + +to_json(Req, #{qmap := QMap} = State) -> + #{ order := Order + , page_size := PageSize + , page := PageNumber} = QMap, + {Users, UsersCount, PagesCount} = get_users(Order, PageSize, PageNumber), + Json = jsx:encode([{page_size, PageSize}, + {page, PageNumber}, + {pages_total, PagesCount}, + {users_total, UsersCount}, + {users, Users}]), + {Json, Req, State}. + +%%%=================================================================== +%%% Internal functions +%%%=================================================================== + +get_users(Ordering, PageSize, PageNumber) -> + Users = lists:keysort(#'Roster'.id, kvs:all('Roster')), + SortedUsers = if Ordering == <<"desc">> -> lists:reverse(Users); true -> Users end, + CuttedUsers = [[{id, UID}, {phone_number, UPhone}] || #'Roster'{id = UID, phone = UPhone} <- SortedUsers], + Offset = (PageNumber - 1) * PageSize, + TotalUsersNumber = length(CuttedUsers), + PaginatedUsers = if Offset >= TotalUsersNumber -> []; true -> roster_channel_helper:paginator(CuttedUsers, PageSize, Offset) end, + TotalPageNumbers = round(TotalUsersNumber / PageSize), + {PaginatedUsers, TotalUsersNumber, TotalPageNumbers}. diff --git a/apps/roster/src/rest/rest_cowboy_whitelist_handler.erl b/apps/roster/src/rest/rest_cowboy_whitelist_handler.erl new file mode 100644 index 0000000000000000000000000000000000000000..0597cc95d8048f4cd27f00c9aae962a78098c03c --- /dev/null +++ b/apps/roster/src/rest/rest_cowboy_whitelist_handler.erl @@ -0,0 +1,113 @@ +%%%------------------------------------------------------------------- +%%% @doc Cowboy handler for /whitelist endpoint +%%% +%%% @end +%%%------------------------------------------------------------------- +-module(rest_cowboy_whitelist_handler). +-include_lib("kernel/include/logger.hrl"). + +%% Cowboy callbacks +-export([ allowed_methods/2 + , content_types_accepted/2 + , content_types_provided/2 + , delete_resource/2 + , init/2 + , is_authorized/2 + , malformed_request/2 + , resource_exists/2 + ]). + +%% Custom callbacks +-export([ to_json/2 + , to_html/2 + , from_json/2 + ]). + + +%%%=================================================================== +%%% API +%%%=================================================================== + +init(#{path := Path, method := Method, qs := QS} = Req, + #{endpoint := Endpoint} = State) when Endpoint =:= whitelist; + Endpoint =:= admin_whitelist + -> + ?LOG_INFO("~s:~s ~p", [Method, Path, QS]), + {cowboy_rest, Req, State}. + +resource_exists(Req, State) -> + {application:get_env(roster, whitelist_check, false), Req, State}. + +is_authorized(Req, State) -> + rest_cowboy_handler:is_authorized(Req, State). + +allowed_methods(Req, #{endpoint := admin_whitelist} = State) -> + {[<<"GET">>], Req, State}; +allowed_methods(Req, #{endpoint := whitelist} = State) -> + {[<<"GET">>, <<"PUT">>, <<"DELETE">>], Req, State}. + +-define(phone_not_found, <<"Cannot find phones in query params. Please, add " + "it in format: phone=PhoneNumber1,PhoneNumber2">>). + +malformed_request(#{method := <<"GET">>} = Req, State) -> + {false, Req, State}; +malformed_request(#{method := <<"DELETE">>} = Req, State) -> + QSList = cowboy_req:parse_qs(Req), + case proplists:get_value(<<"phone">>, QSList) of + undefined -> + {true, set_error_resp(?phone_not_found, Req), State}; + Phone -> + {false, Req, State#{phone => Phone}} + end; +malformed_request(#{method := <<"PUT">>} = Req, State) -> + %% Nothing to check here outside the body, which shouldn't be read here. + {false, Req, State}. + +set_error_resp(Reason, Req) -> + Resp = jsx:encode([{<<"Status">>, <<"Error">>}, + {<<"Reason">>, Reason}]), + cowboy_req:set_resp_body(Resp, Req). + +content_types_provided(Req, #{endpoint := admin_whitelist} = State) -> + {[{<<"text/html">>, to_html} + ], + Req, State}; +content_types_provided(Req, #{endpoint := whitelist} = State) -> + {[{<<"application/json">>, to_json} + ], + Req, State}. + +content_types_accepted(Req, State) -> + {[{<<"application/json">>, from_json} + ], + Req, State}. + +delete_resource(Req, #{phone := Phone} = State) -> + Numbers = re:split(Phone, ","), + Data = rest_whitelist:delete_whitelist(Numbers), + Resp = jsx:encode([{<<"Status">>, <<"Success">>}, {<<"Data">>, Data}]), + Req1 = cowboy_req:set_resp_body(Resp, Req), + {true, Req1, State}. + +to_html(#{method := <<"GET">>} = Req, State) -> + {ok, HTML} = phone_numbers_tpl:render([{title, <<"Whitelist">>}]), + {HTML, Req, State}. + +to_json(#{method := <<"GET">>} = Req, State) -> + Json = rest_whitelist:whitelist_json(), + {Json, Req, State}. + +from_json(#{method := <<"PUT">>} = Req, State) -> + {ok, Data, Req1} = cowboy_req:read_body(Req), + case rest_whitelist:check_put_body(Data) of + {ok, Phones} -> + ok = rest_whitelist:update_whitelist(Phones), + Res = jsx:encode([{<<"Status">>, <<"Success">>}]), + Req2 = cowboy_req:set_resp_body(Res, Req1), + {true, Req2, State}; + {error, Reason} -> + Res = jsx:encode([{<<"Status">>, <<"Error">>}, + {<<"Reason">>, Reason}]), + Req2 = cowboy_req:set_resp_body(Res, Req1), + {false, Req2, State} + end. diff --git a/apps/roster/src/rest/rest_cri.erl b/apps/roster/src/rest/rest_cri.erl index 92345ae854d1baeb84dc934952b739747f447c13..466d4603336c2f915692d4c52e53c0f6a3593bad 100644 --- a/apps/roster/src/rest/rest_cri.erl +++ b/apps/roster/src/rest/rest_cri.erl @@ -1,241 +1,26 @@ -module(rest_cri). --compile({parse_transform, lager_transform}). +-include_lib("kernel/include/logger.hrl"). -include("roster.hrl"). -include("rest_static.hrl"). --include_lib("roster/include/static/rest_var.hrl"). -include_lib("roster/include/static/rest_text.hrl"). -include_lib("roster/include/static/roster_text.hrl"). -include_lib("roster/include/static/roster_var.hrl"). -include_lib("roster/include/static/main_var.hrl"). -export([ - description/0, - handle_request/1, create_message/2 -]). + ]). -description() -> "Call Room Interface Module". - -handle_request(Req)-> - check_headers_missing(Req, [?PHONE_ID_HEADER]). - -%% Room management - -handle_request('GET', ?RCI_ROOM_TYPE_ENDPOINT, Req) -> - QueryData = Req:parse_qs(), - roster:info(?MODULE, "~p:Request:~p:~p", [Req:get(method), Req:get(path), QueryData]), - RoomId = nitro:to_binary(proplists:get_value(nitro:to_binary(?ROOM_ID_PARAM), rest_main_helper:get_query_values(QueryData, [?ROOM_ID_PARAM]))), - {ok, #'Room'{type = Type}} = kvs:get('Room', RoomId), - ResponseData = rest_response_helper:success_response(jsx_tuple_to_json([{?ROOM_TYPE_VALUE, Type}])), - roster:info(?MODULE, "ResponseData:~p", [ResponseData]), - Req:respond({?HTTP_CODE_200, [], ResponseData}); - -handle_request('POST', ?RCI_ROOM_ENDPOINT, Req) -> - roster:info(?MODULE, "~p:Request:~p", [Req:get(method), Req:get(path)]), - RoomId = iolist_to_binary(["conference_", nitro:to_binary(roster:now_msec()), "_", nitro:to_binary(kvs:next_id('Room', 1))]), - AdminPhoneId = Req:get_header_value(?PHONE_ID_HEADER), - {ok, #'Member'{alias = AdminNick, names = AdminName} = AdminMember} = roster:to_member(nitro:to_binary(AdminPhoneId)), -%% create call room name - RoomName = iolist_to_binary(["Call by @", case AdminNick of [] -> AdminName; _ -> AdminNick end]), - rest_main_helper:send_to_mqtt(sync, #'Room'{id = RoomId, status = ?CREATE_STATUS, type = ?CALL_ROOM, name = RoomName, admins = [AdminMember]}), - ResponseData = rest_response_helper:success_response(jsx_tuple_to_json([{?ROOM_ID_PARAM, RoomId}])), - roster:info(?MODULE, "ResponseData:~p", [ResponseData]), - Req:respond({?HTTP_CODE_200, [], ResponseData}); - -handle_request('DELETE', ?RCI_ROOM_ENDPOINT, Req) -> - QueryData = Req:parse_qs(), - roster:info(?MODULE, "~p:Request:~p:~p", [Req:get(method), Req:get(path), QueryData]), - RoomId = nitro:to_binary(proplists:get_value(nitro:to_binary(?ROOM_ID_PARAM), rest_main_helper:get_query_values(QueryData, [?ROOM_ID_PARAM]))), - rest_main_helper:send_to_mqtt(#'Room'{id = RoomId, status = ?DELETE_STATUS, type = ?CALL_ROOM}), - ResponseData = rest_response_helper:success_response(), - roster:info(?MODULE, "ResponseData:~p", [ResponseData]), - Req:respond({?HTTP_CODE_200, [], ResponseData}); - -%% Members management - -handle_request('GET', ?RCI_ROOM_MEMBERS_ENDPOINT, Req) -> - QueryData = Req:parse_qs(), - roster:info(?MODULE, "~p:Request:~p:~p", [Req:get(method), Req:get(path), QueryData]), - ReqParamValues = rest_main_helper:get_query_values(QueryData, [?ROOM_ID_PARAM, ?PHONE_IDS_PARAM]), - RoomId = nitro:to_binary(proplists:get_value(nitro:to_binary(?ROOM_ID_PARAM), ReqParamValues)), - PhoneIdList = proplists:get_value(nitro:to_binary(?PHONE_IDS_PARAM), ReqParamValues), -%% check membership for each phone number - VerifiedPhones = [ - case roster:muc_member(nitro:to_binary(PhoneId), RoomId) of - #'Member'{} -> jsx_tuple_to_json([{?PHONE_ID_PARAM, PhoneId}, {?IS_MEMBER_FLAG, true}]); - [] -> - case rest_main_helper:get_profile_by_phone(rest_main_helper:get_phone_number(PhoneId)) of - {error, _} -> jsx_tuple_to_json([{?PHONE_ID_PARAM, PhoneId}, {?IS_MEMBER_FLAG, false}, {error, ?ERROR_USER_404}]); - _ -> jsx_tuple_to_json([{?PHONE_ID_PARAM, PhoneId}, {?IS_MEMBER_FLAG, false}]) - end - end || PhoneId <- string:tokens(PhoneIdList, ",")], - ResponseData = rest_response_helper:success_response(VerifiedPhones), - roster:info(?MODULE, "ResponseData:~p", [ResponseData]), - Req:respond({?HTTP_CODE_200, [], ResponseData}); - -handle_request('DELETE', ?RCI_ROOM_MEMBERS_ENDPOINT, Req) -> - QueryData = Req:parse_qs(), - roster:info(?MODULE, "~p:Request:~p:~p", [Req:get(method), Req:get(path), QueryData]), - ReqParamValues = rest_main_helper:get_query_values(QueryData, [?ROOM_ID_PARAM, ?PHONE_IDS_PARAM]), - RoomId = nitro:to_binary(proplists:get_value(nitro:to_binary(?ROOM_ID_PARAM), ReqParamValues)), - PhoneIdList = proplists:get_value(nitro:to_binary(?PHONE_IDS_PARAM), ReqParamValues), - AdminPhoneId = Req:get_header_value(?PHONE_ID_HEADER), - {ok, AdminMember} = roster:to_member(nitro:to_binary(AdminPhoneId)), - {ResponseStatus, ResponseData} = request_with_failed_status(string:tokens(PhoneIdList, ","), #'Room'{id = RoomId, status = ?REMOVE_STATUS, admins = [AdminMember]}), - roster:info(?MODULE, "ResponseData:~p", [ResponseData]), - Req:respond({ResponseStatus, [], ResponseData}); - -%% room_id, join - always required -%% if join == false - phone_ids required -handle_request('POST', ?RCI_ROOM_MEMBERS_ENDPOINT, Req) -> - ReqBody = mochiweb_request:recv_body(Req), - roster:info(?MODULE, "~p:Request:~p:~p", [Req:get(method), Req:get(path), ReqBody]), - ReqParamValues = rest_main_helper:get_body_values(ReqBody, [?ROOM_ID_PARAM, ?PHONE_IDS_PARAM, ?JOIN_FLAG_PARAM]), - RoomId = nitro:to_binary(proplists:get_value(nitro:to_binary(?ROOM_ID_PARAM), ReqParamValues)), - IsJoin = nitro:to_binary(proplists:get_value(nitro:to_binary(?JOIN_FLAG_PARAM), ReqParamValues)), - {ResponseStatus, ResponseData} = -%% validate join value - if - IsJoin == <<"true">> -> - AdminPhoneId = Req:get_header_value(?PHONE_ID_HEADER), - {ok, JoinMember} = roster:to_member(nitro:to_binary(AdminPhoneId)), - rest_main_helper:send_to_mqtt(#'Room'{id = RoomId, status = ?JOIN_STATUS, type = ?CALL_ROOM, members = [JoinMember]}), - {?HTTP_CODE_200, rest_response_helper:success_response()}; - IsJoin == <<"false">> -> - case rest_main_helper:get_body_value(ReqBody, ?PHONE_IDS_PARAM) of - {error, PhonesIdParamError} -> {?HTTP_CODE_400, rest_response_helper:error_response(PhonesIdParamError)}; - {ok, PhoneIdList} -> - AdminPhoneId = nitro:to_binary(Req:get_header_value(?PHONE_ID_HEADER)), - case is_admin(AdminPhoneId, RoomId) of - {error, _} -> {?HTTP_CODE_403, rest_response_helper:error_response(?HTTP_CODE_403, ?ERROR_PERMISSION_DENIED)}; - {ok, AdminMember} -> request_with_failed_status(PhoneIdList, #'Room'{id = RoomId, status = ?ADD_STATUS, admins = [AdminMember]}) - end - end; - true -> {?HTTP_CODE_400, rest_response_helper:error_response(?ERROR_INVALID_REQUEST_PARAM)} - end, - roster:info(?MODULE, "ResponseData:~p", [ResponseData]), - Req:respond({ResponseStatus, [], ResponseData}); - -%% Call Bubbles management - -handle_request('POST', ?RCI_BUBBLE_ENDPOINT, Req) -> - ReqBody = mochiweb_request:recv_body(Req), - roster:info(?MODULE, "~p:Request:~p:~p", [Req:get(method), Req:get(path), ReqBody]), - CallBubbleJSON = 'CallBubbleJSONRest':from_json(jsx:decode(ReqBody), #'CallBubbleJSON'{}), - {ResponseStatus, ResponseData} = -%% validate input data - case validate_bubble_data(CallBubbleJSON) of - error -> {?HTTP_CODE_400, rest_response_helper:error_response()}; - ok -> - FromPhoneId = nitro:to_binary(Req:get_header_value(?PHONE_ID_HEADER)), - case validate_permission(FromPhoneId, CallBubbleJSON) of - error -> {?HTTP_CODE_403, rest_response_helper:error_response(?HTTP_CODE_403, ?ERROR_PERMISSION_DENIED)}; - ok -> - Msg = create_message(FromPhoneId, CallBubbleJSON), - rest_main_helper:send_to_mqtt(Msg), - {?HTTP_CODE_200, rest_response_helper:success_response(binary_to_list(term_to_binary(Msg)))} - end - end, - roster:info(?MODULE, "ResponseData:~p", [ResponseData]), - Req:respond({ResponseStatus, [], ResponseData}); - -handle_request(_, _, Req) -> - rest_response_helper:error_405_response(Req). -%% roster:info(?MODULE, "MethodNotAllowed:~p:~p", [Method, Path]), -%% Req:respond({?HTTP_CODE_405, [], []}). +%% For Cowboy handler +-export([request_with_failed_status/2, + validate_bubble_data/1, + validate_permission/2 + ]). %% ------------------------------------------------------------------ %% CRI Local Helpers %% ------------------------------------------------------------------ -checker_error_logs(Req, Error, Status) -> - roster:info(?MODULE, "~p:~p:~p:~p(Query:~p;Body:~p)", [Req:get(method), Req:get(path), Status, Error, Req:parse_qs(), mochiweb_request:recv_body(Req)]). - -check_headers_missing(Req, HeadersList) -> - case rest_main_helper:check_params_missing(HeadersList, rest_main_helper:get_request_headers(Req, HeadersList)) of - {error, _} -> - roster:error(?MODULE, "RequestHeadersProtectedAction", []), - checker_error_logs(Req, Er = ?ERROR_MISSING_HEADER, St = ?HTTP_CODE_400), - Req:respond({St, [], rest_response_helper:error_response(Er)}); - {ok, Headers} -> - roster:info(?MODULE, "Headers:~p", [Headers]), - check_phone_id_header(Req, Headers) - end. - -check_phone_id_header(Req, Headers) -> - case rest_main_helper:get_profile_by_phone(rest_main_helper:get_phone_number(nitro:to_binary(proplists:get_value(nitro:to_binary(?PHONE_ID_HEADER), Headers)))) of - {error, _} -> - roster:error(?MODULE, "ActedUserExistsProtectedAction", []), - checker_error_logs(Req, [], St = ?HTTP_CODE_400), - Req:respond({St, [], rest_response_helper:error_response()}); - {ok, _} -> - Method = Req:get(method), - Path = Req:get(path), - if - (Path == ?RCI_ROOM_TYPE_ENDPOINT andalso Method == 'GET') orelse (Path == ?RCI_ROOM_ENDPOINT andalso Method == 'DELETE') -> check_params_missing(Req, [?ROOM_ID_PARAM], query); - Path == ?RCI_ROOM_MEMBERS_ENDPOINT andalso (Method == 'GET' orelse Method == 'DELETE') -> check_params_missing(Req, [?ROOM_ID_PARAM, ?PHONE_IDS_PARAM], query); - Path == ?RCI_ROOM_MEMBERS_ENDPOINT andalso Method == 'POST' -> check_params_missing(Req, [?ROOM_ID_PARAM, ?JOIN_FLAG_PARAM], body); - true -> handle_request(Req:get(method), Req:get(path), Req) - end - end. - -check_params_missing(Req, ParamsList, query) -> - check_params_missing(Req, ParamsList, Req:parse_qs(), fun rest_main_helper:get_query_values/2); -check_params_missing(Req, ParamsList, body) -> - check_params_missing(Req, ParamsList, mochiweb_request:recv_body(Req), fun rest_main_helper:get_body_values/2). - -check_params_missing(Req, ParamsList, ReqData, Fun) -> - case rest_main_helper:check_params_missing(ParamsList, Fun(ReqData, ParamsList)) of - {error, Er} -> - roster:error(?MODULE, "ParamsExistsProtectedAction", []), - checker_error_logs(Req, Er, St = ?HTTP_CODE_400), - Req:respond({St, [], rest_response_helper:error_response(Er)}); - {ok, Params} -> - check_room_exists(Req, Params) - end. - -check_room_exists(Req, ReqParams) -> - case kvs:get('Room', nitro:to_binary(proplists:get_value(nitro:to_binary(?ROOM_ID_PARAM), ReqParams))) of - {ok, #'Room'{status = Status}} when Status /= delete -> - Method = Req:get(method), - Path = Req:get(path), - if - (Path == ?RCI_ROOM_MEMBERS_ENDPOINT andalso Method == 'DELETE') orelse (Path == ?RCI_ROOM_MEMBERS_ENDPOINT andalso Method == 'POST') orelse (Path == ?RCI_ROOM_ENDPOINT andalso Method == 'DELETE') -> check_room_type(Req, ReqParams); - true -> handle_request(Req:get(method), Req:get(path), Req) - end; - _ -> - roster:error(?MODULE, "RoomExistsProtectedAction", []), - checker_error_logs(Req, Er = ?ERROR_ROOM_NOT_FOUND, St = ?HTTP_CODE_400), - Req:respond({St, [], rest_response_helper:error_response(Er)}) - end. - -%% Check is it regular group or call -check_room_type(Req, ReqParams) -> - case kvs:get('Room', nitro:to_binary(proplists:get_value(nitro:to_binary(?ROOM_ID_PARAM), ReqParams))) of - {ok, #'Room'{type = ?CALL_ROOM}} -> - Method = Req:get(method), - Path = Req:get(path), - if - (Path == ?RCI_ROOM_MEMBERS_ENDPOINT andalso Method == 'DELETE') orelse (Path == ?RCI_ROOM_ENDPOINT andalso Method == 'DELETE') -> check_admin_action(Req, ReqParams); - true -> handle_request(Req:get(method), Req:get(path), Req) - end; - _ -> - roster:error(?MODULE, "RoomTypeProtectedAction", []), - checker_error_logs(Req, Er = ?ERROR_PERMISSION_DENIED, St = ?HTTP_CODE_403), - Req:respond({St, [], rest_response_helper:error_response(Er)}) - end. - -check_admin_action(Req, ReqParams) -> - AdminPhoneId = nitro:to_binary(Req:get_header_value(?PHONE_ID_HEADER)), - RoomId = nitro:to_binary(proplists:get_value(nitro:to_binary(?ROOM_ID_PARAM), ReqParams)), - case is_admin(AdminPhoneId, RoomId) of - {error, _} -> - roster:error(?MODULE, "AdminProtectedAction", []), - checker_error_logs(Req, Er = ?ERROR_PERMISSION_DENIED, St = ?HTTP_CODE_403), - Req:respond({St, [], rest_response_helper:error_response(St, Er)}); - {ok, _} -> handle_request(Req:get(method), Req:get(path), Req) - end. - jsx_tuple_to_json(Data) -> jsx:encode([{nitro:to_binary(K), nitro:to_binary(V)} || {K, V} <- Data]). @@ -253,7 +38,7 @@ failed_json(PhoneList) -> {failed, iolist_to_binary( ["[", string:join( - [binary_to_list(jsx_tuple_to_json([{?PHONE_ID_PARAM, FN}, {error, ?ERROR_USER_404}])) || FN <- PhoneList], + [binary_to_list(jsx_tuple_to_json([{<<"phone_id">>, FN}, {error, ?ERROR_USER_404}])) || FN <- PhoneList], ","), "]"] )} @@ -264,52 +49,44 @@ request_with_failed_status(PhoneIdList, Room) -> %% if Members list is empty - return error and don't do mqtt call %% if exists at least OK member - return 200 and errors list case Members of - [] -> {?HTTP_CODE_400, rest_response_helper:error_response(failed_json(FailedNumbers))}; + [] -> {error, rest_response_helper:error_response(failed_json(FailedNumbers))}; _ -> rest_main_helper:send_to_mqtt(Room#'Room'{type = ?CALL_ROOM, members = Members}), - {?HTTP_CODE_200, rest_response_helper:success_response(failed_json(FailedNumbers))} - end. - -is_admin(PhoneId, RoomId) -> - case roster:muc_member(PhoneId, RoomId) of - Mmbr when is_record(Mmbr, 'Member') andalso Mmbr#'Member'.status == admin -> - {ok, Mmbr}; - _ -> - {error, []} + {ok, rest_response_helper:success_response(failed_json(FailedNumbers))} end. validate_bubble_data(#'CallBubbleJSON'{content_type = ContentType} = Data) -> %% check content type value case lists:member(nitro:to_binary(ContentType), [?CONTENT_TYPE_VIDEOCALL, ?CONTENT_TYPE_AUDIOCALL]) of - false -> roster:error(?MODULE, "InvalidCallBubbleContentType:~p:In:~p", [ContentType, Data]), error; + false -> ?LOG_ERROR("InvalidCallBubbleContentType:~p:In:~p", [ContentType, Data]), error; _ -> validate_type(Data) end. validate_type(#'CallBubbleJSON'{type = CallType} = Data) -> %% check call type value case lists:member(nitro:to_binary(CallType), [?CALL_TYPE_P2P, ?CALL_TYPE_CONFERENCE]) of - false -> roster:error(?MODULE, "InvalidCallBubbleType:~p:In:~p", [CallType, Data]), error; + false -> ?LOG_ERROR("InvalidCallBubbleType:~p:In:~p", [CallType, Data]), error; _ -> validate_feed(nitro:to_binary(CallType), Data) end. validate_feed(?CALL_TYPE_P2P, #'CallBubbleJSON'{feed_id = FeedId} = Data) -> case string:tokens(FeedId, "/") of [From, To] -> validate_feed(#p2p{from = nitro:to_binary(From), to = nitro:to_binary(To)}, Data); - _ -> roster:error(?MODULE, "InvalidCallBubbleFeedId:~p:In:~p", [FeedId, Data]), error + _ -> ?LOG_ERROR("InvalidCallBubbleFeedId:~p:In:~p", [FeedId, Data]), error end; validate_feed(?CALL_TYPE_CONFERENCE, #'CallBubbleJSON'{feed_id = FeedId} = Data) -> validate_feed(#muc{name = nitro:to_binary(FeedId)}, Data); validate_feed(Feed, #'CallBubbleJSON'{type = Type} = Data) -> case kvs:get('writer', Feed) of {ok, #'writer'{}} -> validate_recipients(nitro:to_binary(Type), Data); - _ -> roster:error(?MODULE, "InvalidCallBubbleFeed:~p:In:~p", [Feed, Data]), error + _ -> ?LOG_ERROR("InvalidCallBubbleFeed:~p:In:~p", [Feed, Data]), error end. validate_recipients(?CALL_TYPE_P2P, _) -> ok; validate_recipients(?CALL_TYPE_CONFERENCE, #'CallBubbleJSON'{recipients = Recipients} = Data) -> %% check that recipients is a list of phone_ids case strict_list(Recipients) of - false -> roster:error(?MODULE, "InvalidCallBubbleRecipient:~p:In:~p", [Recipients, Data]), error; + false -> ?LOG_ERROR("InvalidCallBubbleRecipient:~p:In:~p", [Recipients, Data]), error; _ -> ok end. @@ -318,12 +95,12 @@ validate_permission(PhoneId, #'CallBubbleJSON'{type = CallType, feed_id = FeedId validate_permission(PhoneId, #'p2p'{from = U1, to = U2}, Data) -> if PhoneId == U1 orelse PhoneId == U2 -> ok; - true -> roster:error(?MODULE, "PermissionDeniedForUser:~p:In:~p", [PhoneId, Data]), error + true -> ?LOG_ERROR("PermissionDeniedForUser:~p:In:~p", [PhoneId, Data]), error end; validate_permission(PhoneId, #'muc'{name = RoomId}, Data) -> case roster:muc_member(PhoneId, RoomId) of #'Member'{} -> ok; - _ -> roster:error(?MODULE, "PermissionDeniedForUser:~p:In:~p", [PhoneId, Data]), error + _ -> ?LOG_ERROR("PermissionDeniedForUser:~p:In:~p", [PhoneId, Data]), error end. get_feed(?CALL_TYPE_CONFERENCE, FeedId) -> #muc{name = nitro:to_binary(FeedId)}; @@ -352,4 +129,4 @@ create_message(FromPhoneId, #'CallBubbleJSON'{feed_id = FeedId, call_id = Confer files = [#'Desc'{id = DescId, mime = nitro:to_binary(ContentType), payload = nitro:to_binary(Status), data = Features}]}. strict_list(Data) -> - case is_list(Data) of false -> false; _ -> case io_lib:printable_list(Data) of true -> false; _ -> true end end. \ No newline at end of file + case is_list(Data) of false -> false; _ -> case io_lib:printable_list(Data) of true -> false; _ -> true end end. diff --git a/apps/roster/src/rest/rest_deeplink.erl b/apps/roster/src/rest/rest_deeplink.erl deleted file mode 100644 index 9037234e5391eefb1aa109f7fc1d9fc2f5621ca0..0000000000000000000000000000000000000000 --- a/apps/roster/src/rest/rest_deeplink.erl +++ /dev/null @@ -1,36 +0,0 @@ --module(rest_deeplink). --compile({parse_transform, lager_transform}). --include("roster.hrl"). --include("rest_static.hrl"). --include_lib("roster/include/static/rest_var.hrl"). --export([handle_request/3]). - - -%% Request /room?link=LinkValue -%% Response: -%% Success: {"data": {"id": RoomIdString, "name": RoomNameString, "avatar": LinkToRoomAvatarString, "description": RoomDescriptionString}} -%% Failure {"data": "Channel Not Found"} -handle_request('GET', _, Req) -> - QueryData = Req:parse_qs(), - roster:info(?MODULE, "Request:~p:~p:~p", [Req:get(method), Req:get(path), QueryData]), - {ResponseStatus, ResponseData} = case rest_main_helper:get_query_value(QueryData, ?ROOM_LINK_KEY) of - {error, QueryParamError} -> {?HTTP_CODE_400, QueryParamError}; - {ok, LinkValue} -> - case roster_channel_helper:get_channel_by_link(LinkValue) of - [] -> {?HTTP_CODE_404, ?CHANNEL_NOT_FOUND}; - #'Room'{id = Id, name = Name, description = Description} = FoundRoom -> - RoomResponse = [ - {?FOUND_ROOM_ID, Id}, - {?FOUND_ROOM_NAME, Name}, - {?FOUND_ROOM_AVATAR, roster_channel_helper:get_room_avatar(FoundRoom)}, - {?FOUND_ROOM_DESCRIPTION, Description}], - {?HTTP_CODE_200, RoomResponse} - end - end, - roster:info(?MODULE, "ResponseData:~p", [ResponseData]), - Req:respond({ResponseStatus, [], jsx:encode([{<<"data">>, ResponseData}])}); - -handle_request(_, _, Req) -> - rest_response_helper:error_405_response(Req). -%% roster:info(?MODULE, "MethodNotAllowed:~p:~p", [Method, Path]), -%% Req:respond({?HTTP_CODE_405, [], []}). \ No newline at end of file diff --git a/apps/roster/src/rest/rest_fake_numbers.erl b/apps/roster/src/rest/rest_fake_numbers.erl deleted file mode 100644 index cc47233235f0a57ee278c11fe108659c750b1c2e..0000000000000000000000000000000000000000 --- a/apps/roster/src/rest/rest_fake_numbers.erl +++ /dev/null @@ -1,53 +0,0 @@ --module(rest_fake_numbers). --include("roster.hrl"). --include_lib("roster/include/static/rest_var.hrl"). --compile({parse_transform, lager_transform}). --export([handle_request/3]). - -handle_request('GET', "/fake_numbers", Req) -> - roster:info(?MODULE, "~p:GET:~p", [Req:get(path), Req:parse_qs()]), - {ok, HTMLOutput} = phone_numbers_tpl:render([{title, <<"Fake Numbers">>}]), - Req:respond({?HTTP_CODE_200, [{"Content-Type", "text/html"}], HTMLOutput}); - -handle_request('GET', "/fn", Req) -> - roster:info(?MODULE, "~p:GET:~p", [Req:get(path), Req:parse_qs()]), - Res = jsx:encode( - #{<<"Status">> => <<"Success">>, - <<"Data">> => [#{<<"phone">> => binary_to_integer(P), - <<"created">> => list_to_binary(roster:timestamp_to_datetime(CreatedTimestamp))} - || #'FakeNumbers'{created = CreatedTimestamp, phone = P} <- kvs:all('FakeNumbers')]}), -%% roster:info(?MODULE, "Res:~p", [Res]), - Req:respond({?HTTP_CODE_200, [], Res}); - -handle_request('PUT', "/fn", Req) -> - ReqBody = mochiweb_request:recv_body(Req), - roster:info(?MODULE, "~p:PUT:~p", [Req:get(path), ReqBody]), - Error = #{<<"Status">> => <<"Error">>, <<"Reason">> => - <<"Empty Body section. Please, add numbers in format: {\"phone\": [PhoneNumber1,PhoneNumber2]}">>}, - {HttpCode, RawRes} = case catch jsx:decode(ReqBody, [return_maps]) of - #{<<"phone">> := [_|_] = Phones} -> - [roster:add_fake_number(nitro:to_binary(P)) || P <- Phones], - {?HTTP_CODE_200, #{<<"Status">> => <<"Success">>}}; - _ -> {?HTTP_CODE_400, Error} - end, - Req:respond({HttpCode, [], jsx:encode(RawRes)}); - -handle_request('DELETE', "/fn", Req) -> - QueryParams = Req:parse_qs(), - roster:info(?MODULE, "~p:DELETE:~p", [Req:get(path), QueryParams]), - PhoneQueryParams = lists:keyfind("phone", 1, QueryParams), - {HttpCode, RawRes} = - case lists:keyfind("phone", 1, QueryParams) of - false -> - {?HTTP_CODE_400, - #{<<"Status">> => <<"Error">>, - <<"Reason">> => <<"Cannot find phones in query params. Please, add it in format: phone=PhoneNumber1,PhoneNumber2">>}}; - _ -> [kvs:delete('FakeNumbers', nitro:to_binary(P)) || P <- string:tokens(element(2, PhoneQueryParams), ",")], - {?HTTP_CODE_200, #{<<"Status">> => <<"Success">>}} - end, - roster:info(?MODULE, "Res:~p", [RawRes]), - Req:respond({HttpCode, [], jsx:encode(RawRes)}); - -handle_request(Method, Path, Req) -> - roster:info(?MODULE, "MethodNotAllowed:~p:~p", [Method, Path]), - rest_response_helper:response(Req, ?HTTP_CODE_405, rest_response_helper:error_405()). \ No newline at end of file diff --git a/apps/roster/src/rest/rest_handler.erl b/apps/roster/src/rest/rest_handler.erl deleted file mode 100644 index 6f85c2bc176f463b6f4d15bfc8ca6bf6a8c5ca8c..0000000000000000000000000000000000000000 --- a/apps/roster/src/rest/rest_handler.erl +++ /dev/null @@ -1,120 +0,0 @@ --module(rest_handler). --compile({parse_transform, lager_transform}). --include_lib("roster/include/static/rest_text.hrl"). --include_lib("roster/include/static/rest_var.hrl"). --export([init/0, handle_request/1, c_tpl/0, c_tpl/1, c_tpl/2, docroot/0]). - -%% TODO fix with wait() --define(REST_PORT, application:get_env(rest, port, 8888)). --define(OPEN_API_LIST, application:get_env(rest, open_api_list, [])). - -%% Environment variables --define(CHECK_SESSIONS_API, application:get_env(roster, get_sessions_api, false)). --define(CHECK_WHITELIST, application:get_env(roster, whitelist_check, false)). --define(CHECK_FAKE_NUMBERS, application:get_env(roster, fake_numbers_check, false)). - -init() -> - mochiweb:start_http('mqtt:http', ?REST_PORT, [], {rest_handler, handle_request, []}), - roster:info(?MODULE, "REST_PID:~p", [rest_main_helper:rest_pid()]), - c_tpl(). - -%% ------------------------------------------------------------------ -%% Main Module Helpers -%% ------------------------------------------------------------------ - -%% TODO place it to rest_helpers module - -docroot() -> - {file, Here} = code:is_loaded(?MODULE), - Dir = filename:dirname(filename:dirname(Here)), - filename:join([Dir, "priv", "www"]). - -c_tpl() -> -%% compile template (dtl) files - c_tpl([]). -c_tpl(Opts) -> - c_tpl(filelib:wildcard("apps/roster/priv/www/*.dtl"), Opts). -c_tpl([], _Opts) -> ok; -c_tpl([File | Files], Opts) -> - ok = erlydtl:compile(File, re:replace(filename:basename(File), ".dtl", "_tpl", [global, {return, list}]), Opts), - c_tpl(Files, Opts). - -%% ------------------------------------------------------------------ -%% Rest Handlers Routing -%% ------------------------------------------------------------------ - -handle_request(Req) -> - roster:info(?MODULE, "Method: ~p Path: ~p Requset: ~p", [Req:get(method), Req:get(path), Req:parse_qs()]), - Path= case Req:get(path) of "/" ++ P -> P; _ ->"/" end, -%% NOTE extra check for "favicon.ico" file on rest requests without html body - case Path of - "favicon.ico" -> Req:serve_file("assets/img/favicon.ico", docroot()); - _ -> - %% check is it static file - case string:find(Path, "assets/") of - nomatch -> -%% /test - case lists:member(nitro:to_binary(Req:get(path)), ?OPEN_API_LIST) of - true -> handle_request(Req:get(method), Req:get(path), Req); - _ -> case rest_auth_helper:authorized(Req) of - false -> rest_response_helper:response(Req, ?HTTP_CODE_401, rest_response_helper:error_401()); - {error, invalid_token} -> rest_response_helper:error_response_api(Req, ?HTTP_CODE_401, "Token is invalid."); - {error, token_expired} -> rest_response_helper:error_response_api(Req, ?HTTP_CODE_401, "Token is expired."); - {true, api} -> handle_request2(Req:get(method), Req:get(path), Req); %% We hit here if authentication is Bearer (Not Basic) - _ -> handle_request(Req:get(method), Req:get(path), Req) - end - end; - _ -> - Req:serve_file(Path, docroot()) - end - end. - -handle_request(Method, "/chat" ++ _ = P, Req) -> - ["chat", PhoneId, "message"] = lists:delete([], string:split(P, "/", all)), - rest_message:handle_request(Method, #{user => list_to_binary(PhoneId)}, Req); - -handle_request(Method, "/sessions" = Path, Req) -> case ?CHECK_SESSIONS_API of true -> rest_session :handle_request(Method, Path, Req); _ -> handle_request_404(Method, Path, Req) end; -handle_request(Method, "/whitelist" = Path, Req) -> case ?CHECK_WHITELIST of true -> rest_whitelist :handle_request(Method, Path, Req); _ -> handle_request_404(Method, Path, Req) end; -handle_request(Method, "/admin_whitelist" = Path, Req) -> case ?CHECK_WHITELIST of true -> admin_whitelist :handle_request(Method, Path, Req); _ -> handle_request_404(Method, Path, Req) end; -handle_request(Method, Path, Req) - when Path == "/fake_numbers"; Path == "/fn" -> case ?CHECK_FAKE_NUMBERS of true -> rest_fake_numbers:handle_request(Method, Path, Req); _ -> handle_request_404(Method, Path, Req) end; - - -%% NOTE! uncomment the row under to turn on call bubbles -%% handle_request(_, ?RCI_BUBBLE_ENDPOINT, Req) -> rest_cri :handle_request(Req); -handle_request(Method, "/metrics" = Path, Req) -> rest_metric :handle_request(Method, Path, Req); -handle_request(Method, ?MSG_PUSH_ENDPOINT = Path, Req) -> rest_push:handle_request(Method, Path, Req); -handle_request(Method, ?ROOM_ENDPOINT = Path, Req) -> rest_deeplink:handle_request(Method, Path, Req); -handle_request(Method, ?PUBLISH_ENDPOINT = Path, Req) -> rest_publish:handle_request(Method, Path, Req); -handle_request(Method, ?USERS_ENDPOINT = Path, Req) -> rest_users:handle_request(Method, Path, Req); -handle_request(_, "/cri/" ++ _ = Path, Req) - when Path == ?RCI_ROOM_TYPE_ENDPOINT; Path == ?RCI_ROOM_ENDPOINT; Path == ?RCI_ROOM_MEMBERS_ENDPOINT; - Path == ?RCI_BUBBLE_ENDPOINT -> rest_cri:handle_request(Req); -handle_request(Method, ?METRICS_ENDPOINT = Path, Req) -> rest_metric:handle_request(Method, Path, Req); - -handle_request('GET', ?TEST_API_ENDPOINT, Req) -> - roster:info(?MODULE, "~p:~p", [Req:get(method), Req:get(path)]), - Res = mochijson:encode({struct, [{<<"Current time">>, roster:timestamp_to_datetime(roster:now_msec())}]}), - rest_response_helper:response(Req, ?HTTP_CODE_200, Res); - -handle_request(Method, "/link/" ++ _ = Path, Req) -> - ["link", LinkId, "room_id"] = lists:filter(fun (Elem) -> Elem /= [] end, string:split(Path, "/", all)), - rest_link:handle_request(Method, #{link_id => LinkId}, Req); - -handle_request(Method, Path, Req) -> handle_request_404(Method, Path, Req). - -handle_request_404(Method, Path, Req) -> - roster:info(?MODULE, "UnexpectedHTTPRequest:~p:~p", [Method, Path]), - rest_response_helper:response(Req, ?HTTP_CODE_404, rest_response_helper:error_404()). - -%% Seperate handler funcions for routing calls under /api/ -%% All calls under /api/ are opened to the world throuth https (this is not done in the erlang code) -handle_request2(Method, "/api" ++ Path, Req) -> handle_request2(Method, Path, Req); -handle_request2(Method, "/v1" ++ Path, Req) -> handle_request2_v1(Method, lists:delete([], string:split(Path, "/", all)), Req). - -%% Handler functions for version 1 of API Protocol -handle_request2_v1(Method, ["groups", RoomId, "messages", "csv"], Req) -> rest_chat_csv:handle_request(Method, #{room => RoomId}, Req); -handle_request2_v1(Method, ["chats", PhoneId, "messages", "csv"], Req) -> rest_chat_csv:handle_request(Method, #{user => PhoneId}, Req); -handle_request2_v1(Method, ["groups", RoomId, "messages", MessageId, "transcribe"], Req) -> rest_transcribe:handle_request(Method, #{room => RoomId, message => MessageId}, Req); -handle_request2_v1(Method, ["chats", PhoneId, "messages", MessageId, "transcribe"], Req) -> rest_transcribe:handle_request(Method, #{user => PhoneId, message => MessageId}, Req); -handle_request2_v1(_, _, Req) -> rest_response_helper:error_response_api(Req, ?HTTP_CODE_400, "Bad request"). \ No newline at end of file diff --git a/apps/roster/src/rest/rest_link.erl b/apps/roster/src/rest/rest_link.erl deleted file mode 100644 index 834e1090bf4b2d4d37e3e3c9ebd836cb3dccbbef..0000000000000000000000000000000000000000 --- a/apps/roster/src/rest/rest_link.erl +++ /dev/null @@ -1,30 +0,0 @@ --module(rest_link). --include("roster.hrl"). --include_lib("roster/include/static/rest_var.hrl"). --include_lib("kvs/include/metainfo.hrl"). - --export([ - handle_request/3 -]). - -handle_request('GET', #{link_id := LinkId}, Req) -> - {StatusCode, ResponseData} = - case kvs:get('Link', list_to_binary(LinkId)) of - {ok, #'Link'{room_id = RoomId}} -> - case kvs:get('Room', RoomId) of - {ok, #'Room'{name = RoomName}} -> - {?HTTP_CODE_200, [{<<"room_id">>, RoomId}, {<<"room_name">>, RoomName}]}; - _ -> - {?HTTP_CODE_404, [{<<"error">>, <<"not_found">>}, {<<"room_id">>, list_to_binary(RoomId)}]} - end; - {error, not_found} -> - {?HTTP_CODE_404, [{<<"error">>, <<"not_found">>}, {<<"link_id">>, list_to_binary(LinkId)}]} - end, - send_response(Req, ResponseData, StatusCode). - -send_response(Req, ResponseData, StatusCode) -> - roster:info(?MODULE, "ResponseData:~p", [ResponseData]), - ResponseHeader = [{"Content-Type", "application/json"}], - Req:respond({StatusCode, ResponseHeader, jsx:encode(ResponseData)}). - - \ No newline at end of file diff --git a/apps/roster/src/rest/rest_message.erl b/apps/roster/src/rest/rest_message.erl deleted file mode 100644 index eb08c447bbdbfb4b87b9e885e92a63e33600e16f..0000000000000000000000000000000000000000 --- a/apps/roster/src/rest/rest_message.erl +++ /dev/null @@ -1,63 +0,0 @@ --module(rest_message). --include("roster.hrl"). --include_lib("roster/include/static/rest_var.hrl"). --include_lib("kvs/include/metainfo.hrl"). --export([ - handle_request/3 -]). - -%%%============================================================================= -%%% API -%%%============================================================================= -handle_request('GET', #{user := Receiver}, Req) -> - {SenderType, Sender, Message} = parse_get(Req:parse_qs()), - try get_users([Sender, Receiver]) of - [{ok, SenderPhoneId}, {ok, ReceiverPhoneId} | _] -> - io:format("Sender: ~p~nReceiver: ~p~n", [SenderPhoneId, ReceiverPhoneId]), - {Code, Response} = - case rest_gw:add_as_friend(SenderType, SenderPhoneId, ReceiverPhoneId) of - ok -> - case rest_gw:send_p2p_message(SenderPhoneId, ReceiverPhoneId, Message) of - ok -> {?HTTP_CODE_200, rest_response_helper:success_response(<<"ok_message_sent">>, <<"Message send to: ", Receiver/binary>>)}; - {error, send_message} -> {?HTTP_CODE_400, rest_response_helper:error_response(<<"error_sending_message">>, <<"Error sending Message to: ", Receiver/binary>>)} - end; - friend_request -> - {?HTTP_CODE_200, rest_response_helper:error_response(<<"ok_request_friend">>, <<"Friend request send to: ", Receiver/binary>>)}; - {error, request_not_accepted} -> - {?HTTP_CODE_400, rest_response_helper:error_response(<<"error_request_not_accepted">>, <<"Friend request not accepted by: ", Receiver/binary>>)}; - {error, send_friend_request} -> - {?HTTP_CODE_400, rest_response_helper:error_response(<<"error_send_friend_request">>, <<"Error Request Friend/request with: ", Receiver/binary>>)}; - {error, confirm_friend_request} -> - {?HTTP_CODE_400, rest_response_helper:error_response(<<"error_confirm_friend_request">>, <<"Error Request Friends/confirm with: ", Receiver/binary>>)}; - {error, unexpected_error} -> - {?HTTP_CODE_400, rest_response_helper:error_response(<<"error_unexpected">>, <<"Unexpected Error with: ", Receiver/binary>>)} - end, - send_response(Code, Response, Req) - catch - {error, not_found, User} -> send_response(?HTTP_CODE_400, rest_response_helper:error_response(<<"error">>, <<"User not found: ", User/binary>>), Req); - {error, bad_uuid, User} -> send_response(?HTTP_CODE_400, rest_response_helper:error_response(<<"error">>, <<"Bad User UUID: ", User/binary>>), Req) - end. - -send_response(StatusCode, Response, Req) -> - ResponseHeader = [{"Content-Type", "application/json"}], - roster:info(?MODULE, "StatusCode: ~p / Response: ~p~n", [StatusCode, Response]), - Req:respond({StatusCode, ResponseHeader, Response}). - -parse_get(ReqParams) -> - Sender = list_to_binary(proplists:get_value("sender", ReqParams)), - SenderType = list_to_binary(proplists:get_value("sender_type", ReqParams, "")), - Message = list_to_binary(proplists:get_value("text", ReqParams, "")), - {SenderType, Sender, Message}. - -get_users(Users) -> - [get_user(User) || User <- Users]. - -get_user(RosterUUID) -> - case catch uuid:is_valid(binary_to_list(RosterUUID)) of - true -> - case micro:uuid_to_id('LinkRoster', RosterUUID) of - [] -> throw({error, not_found, RosterUUID}); - RosterId -> {ok, roster:phone_id(RosterId)} - end; - {'EXIT', _} -> throw({error, bad_uuid, RosterUUID}) - end. \ No newline at end of file diff --git a/apps/roster/src/rest/rest_metric.erl b/apps/roster/src/rest/rest_metric.erl deleted file mode 100644 index 96058662c4d1d54a77a1e58dee2795ee0e637c12..0000000000000000000000000000000000000000 --- a/apps/roster/src/rest/rest_metric.erl +++ /dev/null @@ -1,22 +0,0 @@ --module(rest_metric). --compile({parse_transform, lager_transform}). --include("roster.hrl"). --include_lib("roster/include/static/prometheus_var.hrl"). --include_lib("roster/include/static/rest_var.hrl"). --export([handle_request/3]). - - -handle_request('GET', _, Req) -> - roster:info(?MODULE, "GET:~p", [Req:get(path)]), - prometheus_gauge:set(?METRIC_CHATS_TOTAL, [?METRIC_LABEL_P2P_CHAT], roster_db:p2p_stats()), - prometheus_gauge:set(?METRIC_CHATS_TOTAL, [?METRIC_LABEL_GROUP_CHAT], roster_db:room_stats()), - [prometheus_gauge:set(?METRIC_USERS_PER_COUNTRY, [Country], Value) || {Country, Value} <- roster_db:country_user_stats()], -% [prometheus_gauge:set(?METRIC_USERS_PER_COUNTRY_ONLINE, [Country], Value) || {Country, Value} <- roster_db:country_user_stats(online)], - [prometheus_gauge:set(?METRIC_MSGS_BY_TYPE_NMBR, [Mime], Value) || {Mime, Value} <- roster_db:msg_stats()], - [prometheus_gauge:set(?METRIC_SESSIONS_PER_COUNTRY_ONLINE, [Country], Value) || {Country, Value} <- roster_db:country_session_stats(online)], - rest_response_helper:response(Req, 200, prometheus_text_format:format(), "text/plain"); - -handle_request(_, _, Req) -> - rest_response_helper:error_405_response(Req). -%% roster:info(?MODULE, "MethodNotAllowed:~p:~p", [Method, Path]), -%% Req:respond({?HTTP_CODE_405, [], []}). \ No newline at end of file diff --git a/apps/roster/src/rest/rest_publish.erl b/apps/roster/src/rest/rest_publish.erl deleted file mode 100644 index e4c1464705c1cb495c363af7a14193f982c368e9..0000000000000000000000000000000000000000 --- a/apps/roster/src/rest/rest_publish.erl +++ /dev/null @@ -1,42 +0,0 @@ --module(rest_publish). --compile({parse_transform, lager_transform}). --include("roster.hrl"). --include_lib("roster/include/static/rest_text.hrl"). --include_lib("roster/include/static/rest_var.hrl"). --export([description/0, handle_request/3]). - -description() -> "Publish any message to the topic". - -handle_request(Method, Path, Req) when Method == 'POST' -> - ReqBody = mochiweb_request:recv_body(Req), - roster:info(?MODULE, "~p:~p:Request:~p", [Method, Path, ReqBody]), - {ResponseStatus, Response} = - case jsx:is_json(ReqBody) of - false -> - {?HTTP_CODE_400, rest_response_helper:error_response(?ERROR_INVALID_JSON)}; - _ -> - #'PublishService'{message = Msg, topic = Topic, qos = Qos} = 'PublishServiceRest':from_json(jsx:decode(ReqBody), #'PublishService'{}), - case (Msg == [] orelse Topic == []) of - true -> - {?HTTP_CODE_400, rest_response_helper:error_response(?ERROR_MISSING_PARAM)}; - _ -> - case lists:member(Qos, lists:seq(0, 2)) of - false -> {?HTTP_CODE_400, rest_response_helper:error_response(?ERROR_INVALID_QOS_PARAM)}; - _ -> - case rest_main_helper:validate_msg_to_publish(Msg) of - false -> {?HTTP_CODE_400, rest_response_helper:error_response(?ERROR_INVALID_MESSAGE_PARAM)}; - DecodedMsg -> -%% publish to mqtt topic - n2o_vnode:send(rest_main_helper:rest_pid(), Topic, term_to_binary(DecodedMsg), [{qos, Qos}]), - {?HTTP_CODE_200, rest_response_helper:success_200()} - end - end - end - end, - roster:info(?MODULE, "~p:~p:Response:~p", [Method, Path, Response]), - rest_response_helper:response(Req, ResponseStatus, Response); - -handle_request(_, _, Req) -> - rest_response_helper:error_405_response(Req). -%% roster:info(?MODULE, "MethodNotAllowed:~p:~p", [Method, Path]), -%% rest_response_helper:response(Req, ?HTTP_CODE_405, rest_response_helper:error_405()). \ No newline at end of file diff --git a/apps/roster/src/rest/rest_push.erl b/apps/roster/src/rest/rest_push.erl deleted file mode 100644 index 21fb0cf1c5c924b0a42f3f859d9b41b632fb717e..0000000000000000000000000000000000000000 --- a/apps/roster/src/rest/rest_push.erl +++ /dev/null @@ -1,42 +0,0 @@ --module(rest_push). --compile({parse_transform, lager_transform}). --include("roster.hrl"). --include_lib("roster/include/static/rest_var.hrl"). --export([handle_request/3]). - -%% NOTE! Link for API design https://nynjadev.atlassian.net/wiki/spaces/SER/pages/119799882/REST+API - -handle_request('POST', _, Req) -> - ReqBody = mochiweb_request:recv_body(Req), - roster:info(?MODULE, "~p:~p:Request:~p", [Req:get(method), Req:get(path), ReqBody]), - ResponseStatus = - case jsx:is_json(ReqBody) of - false -> - roster:info(?MODULE, "Error Invalid Json", []), - ?HTTP_CODE_400; - _ -> -%% "payload" field is json -%% 'PushServiceRest':from_json/1 method makes it erlang json ([{key, value}, ..., {keyN, valueN}]) -%% server should encode it to json again after mapping on PushServiceRest model - #'PushService'{payload = Payload, recipients = Recipient, module = Module} = 'PushServiceRest':from_json(jsx:decode(ReqBody), #'PushService'{}), -%% also server should make all values from string to binary because erlang works with binary() data - PushAlert = PushType = list_to_binary(Module), - [[n2o_async:pid(system, roster_push) ! {async_push, Auth, list_to_binary(Payload), PushAlert, PushType} - || Auth <- kvs:index('Auth', user_id, PhoneId)] || PhoneId <- list_elements_to_binary(Recipient)], - ?HTTP_CODE_200 - end, - roster:info(?MODULE, "~p:~p:Response:~p", [Req:get(method), Req:get(path), ResponseStatus]), - Req:respond({ResponseStatus, [], []}); - -handle_request(_, _, Req) -> - rest_response_helper:error_405_response(Req). -%% roster:info(?MODULE, "MethodNotAllowed:~p:~p", [Method, Path]), -%% Req:respond({?HTTP_CODE_405, [], []}). - -list_elements_to_binary(Data) -> - lists:foldl(fun(Val, Res) -> - case is_list(Val) of - false -> Res ++ [Val]; - _ -> Res ++ [list_to_binary(Val)] - end - end, [], Data). \ No newline at end of file diff --git a/apps/roster/src/rest/rest_session.erl b/apps/roster/src/rest/rest_session.erl deleted file mode 100644 index fada62335d2779eed7b0ae6c44a3bb57c8f9f7b0..0000000000000000000000000000000000000000 --- a/apps/roster/src/rest/rest_session.erl +++ /dev/null @@ -1,52 +0,0 @@ --module(rest_session). --compile({parse_transform, lager_transform}). --include("roster.hrl"). --include_lib("roster/include/static/rest_var.hrl"). --export([handle_request/3]). - -%% TODO refactor me! - -handle_request(Method, Path, Req) when Method == 'GET' -> - roster:info(?MODULE, "~p:GET:~p", [Req:get(path), Req:parse_qs()]), - FirstQueryParamList = lists:sublist(Req:parse_qs(), 1), - {ResponseStatus, ResponseBody} = case length(FirstQueryParamList) of - L when L == 0 -> {?HTTP_CODE_400, rest_response_helper:error_response(<<"Empty query parameter. Please, add it in format: phone=RequestPhoneNumber">>)}; - _ -> - FirstQueryParam = lists:last(FirstQueryParamList), - case element(1, FirstQueryParam) of - "phone" -> - Phone = element(2, FirstQueryParam), - Sessions = kvs:index('Auth', phone, list_to_binary(Phone)), - case Sessions of - [] -> {?HTTP_CODE_400, rest_response_helper:error_response(<<"Cannot find sessions for requested number. Please, check input query parameters">>)}; - _ -> - R = iolist_to_binary([<<"[">>, - roster:binary_join( - lists:flatten([begin - jsx:encode('AuthRest':to_json(S#'Auth'{settings = - iolist_to_binary([<<"[">>, - roster:binary_join( - lists:flatten([begin - jsx:encode('FeatureRest':to_json(Feature)) - end || Feature <- FeaturesList]), - <<",">>), - <<"]">>])})) - end || #'Auth'{settings = FeaturesList} = S <- Sessions]), - <<",">>), - <<"]">>]), - {?HTTP_CODE_200, re:replace(re:replace(re:replace(re:replace(re:replace( - R, "\\\\\\\"", "\\\"", [{return, list}, global]), - "\\]\\\"", "\\]", [{return, list}, global]), - "\\\"\\[", "\\[", [{return, list}, global]), - "\\\"\\{", "\\{", [{return, list}, global]), - "\\}\\\"", "\\}", [{return, list}, global])} - end; - _ -> {?HTTP_CODE_400, rest_response_helper:error_response(<<"Invalid query parameter. Please, add it in format: phone=RequestPhoneNumber">>)} - end - end, - roster:info(?MODULE, "~p:~p:Response:~p", [Method, Path, ResponseBody]), - rest_response_helper:response(Req, ResponseStatus, ResponseBody); - -handle_request(Method, Path, Req) -> - roster:info(?MODULE, "MethodNotAllowed:~p:~p", [Method, Path]), - rest_response_helper:response(Req, ?HTTP_CODE_405, rest_response_helper:error_405()). \ No newline at end of file diff --git a/apps/roster/src/rest/rest_transcribe.erl b/apps/roster/src/rest/rest_transcribe.erl deleted file mode 100644 index dede61b152f406b9ff6cdd01d79645e02c706b38..0000000000000000000000000000000000000000 --- a/apps/roster/src/rest/rest_transcribe.erl +++ /dev/null @@ -1,84 +0,0 @@ -%%%----------------------------------------------------------------------------- -%%% @doc API module for transcription and translation of an audio message -%%% -%%% Here we expose a GET request for transcription of an audio message. -%%% The GET request requires from_lang (language of the audio message) -%%% and to_lang (language to transcribed text should be translated). -%%% -%%% @see rest_handler:handle_request/3 -%%% @end -%%%----------------------------------------------------------------------------- --module(rest_transcribe). --include("roster.hrl"). --include_lib("roster/include/static/rest_var.hrl"). --include_lib("kvs/include/metainfo.hrl"). - --export([ - handle_request/3 -]). - -%%%============================================================================= -%%% API -%%%============================================================================= - -handle_request('GET', #{room := RoomId, message := MessageId}, Req) -> - ReqData = Req:parse_qs(), - roster:info(?MODULE, "~p:Request:~p:~p", [Req:get(method), Req:get(path), ReqData]), - - case parse_incomming_data(ReqData) of - {ok, Lang} -> - try - Message = get_message(string:to_integer(MessageId)), - io:format("Lang:~p~nMsg:~p~n", [Lang, MessageId]), - Transcription = jsx:encode([{<<"transcription">>, <<"hardcoded transcription of text">>}]), - send_response(Transcription, Req) - catch - throw:{msg_not_found, Id} -> - rest_response_helper:error_response(Req, ?HTTP_CODE_404, io_lib:format("Message with ID=~i not found", [Id])); - _:_ -> - rest_response_helper:error_response(Req, ?HTTP_CODE_404, "Something went wrong!") - end; - {error, wrong_data} -> - rest_response_helper:error_400_response(Req) - end; - -handle_request('GET', #{user := UserId, message := MessageId}, Req) -> - ReqData = Req:parse_qs(), - roster:info(?MODULE, "~p:Request:~p:~p", [Req:get(method), Req:get(path), ReqData]), - - case parse_incomming_data(ReqData) of - {ok, Lang} -> - try - Message = get_message(string:to_integer(MessageId)), - io:format("Lang:~p~nMsg:~p~n", [Lang, MessageId]), - Transcription = jsx:encode([{<<"transcription">>, <<"hardcoded transcription of text">>}]), - send_response(Transcription, Req) - catch - throw:{msg_not_found, Id} -> - rest_response_helper:error_response(Req, ?HTTP_CODE_404, io_lib:format("Message with ID=~i not found", [Id])); - _:_ -> - rest_response_helper:error_response(Req, ?HTTP_CODE_404, "Something went wrong!") - end; - {error, wrong_data} -> - rest_response_helper:error_400_response(Req) - end; - -handle_request(_, _, Req) -> - rest_response_helper:error_405_response(Req). - -send_response(Transcription, Req) -> - {ResponseStatus, ResponseData} = {?HTTP_CODE_200, Transcription}, - ResponseHeader = [{"Content-Type", "applicaition/json"}], - roster:info(?MODULE, "ResponseData:~p", [ResponseData]), - Req:respond({ResponseStatus, ResponseHeader, ResponseData}). - --spec parse_incomming_data(list(tuple())) -> {ok, term()} | {error, wrong_data}. -parse_incomming_data([{"lang", Lang}]) -> {ok, Lang}; -parse_incomming_data(_) -> {error, wrong_data}. - - -get_message({MsgId, []}) -> - case kvs:get('Message', MsgId) of - {ok, Msg} -> Msg; - {error, not_found} -> throw({msg_not_found, MsgId}) - end. \ No newline at end of file diff --git a/apps/roster/src/rest/rest_users.erl b/apps/roster/src/rest/rest_users.erl deleted file mode 100644 index 1ea97604400dcb4b07cb07d3ade3c09353987120..0000000000000000000000000000000000000000 --- a/apps/roster/src/rest/rest_users.erl +++ /dev/null @@ -1,84 +0,0 @@ --module(rest_users). --compile({parse_transform, lager_transform}). --include("roster.hrl"). --include("rest_static.hrl"). --include_lib("roster/include/static/rest_text.hrl"). --include_lib("roster/include/static/roster_text.hrl"). --include_lib("roster/include/static/roster_var.hrl"). --include_lib("roster/include/static/main_var.hrl"). - --export([ - description/0, - handle_request/3 -]). - -description() -> "Users Interface Module". - -handle_request('GET', _, Req) -> - ReqData = Req:parse_qs(), - roster:info(?MODULE, "~p:Request:~p:~p", [Req:get(method), Req:get(path), ReqData]), - Ordering = get_ordering_param(ReqData), - SizeCount = get_size_param(ReqData), - PageNumber = get_page_param(ReqData), - {Users, UsersCount, PagesCount} = get_users(Ordering, SizeCount, PageNumber), - {ResponseStatus, ResponseData} = {200, jsx:encode([ - {page_size, SizeCount}, - {page, PageNumber}, - {pages_total, PagesCount}, - {users_total, UsersCount}, - {users, Users}])}, - roster:info(?MODULE, "ResponseData:~p", [ResponseData]), - Req:respond({ResponseStatus, [], ResponseData}); - -handle_request(_, _, Req) -> - rest_response_helper:error_405_response(Req). -%% roster:info(?MODULE, "MethodNotAllowed:~p:~p", [Method, Path]), -%% Req:respond({405, [], []}). - -%% ------------------------------------------------------------------ -%% Users Local Helpers -%% ------------------------------------------------------------------ - -get_ordering_param(RequestQuery) -> - Param = "order", - DefaultValue = <<"desc">>, - case rest_main_helper:get_query_value(RequestQuery, Param) of - {ok, Val} when Val == "asc" -> nitro:to_binary(Val); - _ -> DefaultValue - end. - -get_size_param(RequestQuery) -> - Param = "page_size", - DefaultValue = 50, - - case rest_main_helper:get_query_value(RequestQuery, Param) of - {ok, Val} -> - try - ReqParam = binary_to_integer(nitro:to_binary(Val)), - if (ReqParam > DefaultValue) orelse (ReqParam =< 0) -> DefaultValue; true -> ReqParam end - catch _:_ -> DefaultValue end; - _ -> DefaultValue - end. - -get_page_param(RequestQuery) -> - Param = "page", - DefaultValue = 1, - - case rest_main_helper:get_query_value(RequestQuery, Param) of - {ok, Val} -> - try - ReqParam = binary_to_integer(nitro:to_binary(Val)), - if ReqParam > 0 -> ReqParam; true -> DefaultValue end - catch _:_ -> DefaultValue end; - _ -> DefaultValue - end. - -get_users(Ordering, PageSize, PageNumber) -> - Users = lists:keysort(#'Roster'.id, kvs:all('Roster')), - SortedUsers = if Ordering == <<"desc">> -> lists:reverse(Users); true -> Users end, - CuttedUsers = [[{id, UID}, {phone_number, UPhone}] || #'Roster'{id = UID, phone = UPhone} <- SortedUsers], - Offset = (PageNumber - 1) * PageSize, - TotalUsersNumber = length(CuttedUsers), - PaginatedUsers = if Offset >= TotalUsersNumber -> []; true -> roster_channel_helper:paginator(CuttedUsers, PageSize, Offset) end, - TotalPageNumbers = round(TotalUsersNumber / PageSize), - {PaginatedUsers, TotalUsersNumber, TotalPageNumbers}. \ No newline at end of file diff --git a/apps/roster/src/rest/rest_whitelist.erl b/apps/roster/src/rest/rest_whitelist.erl index 194373a3c17368ee11f3fc6381c01400d4174656..02b677ad3092898347d5afa915a2a140cb2e85ff 100644 --- a/apps/roster/src/rest/rest_whitelist.erl +++ b/apps/roster/src/rest/rest_whitelist.erl @@ -1,13 +1,16 @@ -module(rest_whitelist). -include("roster.hrl"). --include_lib("roster/include/static/rest_var.hrl"). --compile({parse_transform, lager_transform}). --export([handle_request/3]). + +-export([ whitelist_json/0 + , check_put_body/1 + , update_whitelist/1 + , delete_whitelist/1 + ]). %% TODO refactor me! -handle_request('GET', "/whitelist", Req) -> - roster:info(?MODULE, "~p:GET:~p", [Req:get(path), Req:parse_qs()]), + +whitelist_json() -> WhitelistRes = iolist_to_binary([<<"[">>, roster:binary_join( lists:flatten([begin @@ -20,96 +23,65 @@ handle_request('GET', "/whitelist", Req) -> jsx:encode([{<<"Status">>, <<"Success">>}, {<<"Data">>, WhitelistRes}]), "\\\\\\\"", "\\\"", [{return, list}, global]), "\\]\\\"", "\\]", [{return, list}, global]), "\\\"\\[", "\\[", [{return, list}, global]), - roster:info(?MODULE, "Res:~p", [WhitelistRes]), - Req:respond({?HTTP_CODE_200, [], Res}); + Res. -handle_request('PUT', "/whitelist", Req) -> - ReqBody = mochiweb_request:recv_body(Req), - roster:info(?MODULE, "~p:PUT:~p", [Req:get(path), ReqBody]), - Res = jsx:encode(case ReqBody of - <<>> -> - [{<<"Status">>, <<"Error">>}, {<<"Reason">>, - <<"Empty Body section. Please, add numbers in format: {\"phone\": [PhoneNumber1,PhoneNumber2]}">>}]; - _ -> - case jsx:is_json(ReqBody) of - false -> - [{<<"Status">>, <<"Error">>}, {<<"Reason">>, - <<"Incorrect Body section. Please, add numbers in format: {\"phone\": [PhoneNumber1,PhoneNumber2]}">>}]; - _ -> - PhoneElement = lists:keyfind(<<"phone">>, 1, jsx:decode(ReqBody)), - case lists:keyfind(<<"phone">>, 1, jsx:decode(ReqBody)) of - false -> - [{<<"Status">>, <<"Error">>}, {<<"Reason">>, - <<"Cannot find phones. Please, add numbers in format: {\"phone\": [PhoneNumber1,PhoneNumber2]}">>}]; - _ -> - Phones = element(2, PhoneElement), - case is_list(Phones) of - false -> - [{<<"Status">>, <<"Error">>}, {<<"Reason">>, - <<"Incorrect phones data format. Please, add numbers in format: {\"phone\": [PhoneNumber1,PhoneNumber2]}">>}]; - _ -> - case Phones of - [] -> - [{<<"Status">>, <<"Error">>}, {<<"Reason">>, - <<"Empty phones list. Please, add numbers in format: {\"phone\": [PhoneNumber1,PhoneNumber2]}">>}]; - _ -> - case lists:member(false, lists:flatten([begin is_integer(P) end || P <- Phones])) of - true -> - [{<<"Status">>, <<"Error">>}, {<<"Reason">>, - <<"Incorrect phone format. All phone numbers should be integers">>}]; - _ -> - Whitelist = kvs:all('Whitelist'), - [begin - BinPh = integer_to_binary(P), - case lists:keyfind(BinPh, #'Whitelist'.phone, Whitelist) of - false -> - kvs:put(#'Whitelist'{phone = BinPh, created = roster:now_msec()}); - _ -> - pass - end - end || P <- Phones], - [{<<"Status">>, <<"Success">>}] - end - end - end - end - end - end), - roster:info(?MODULE, "Res:~p", [Res]), - Req:respond({case string:str(binary_to_list(Res), "Error") of 0 -> ?HTTP_CODE_200; _ -> ?HTTP_CODE_400 end, [], Res}); +check_put_body(ReqBody) -> + case ReqBody of + <<>> -> + {error, <<"Empty Body section. Please, add numbers in format: {\"phone\": [PhoneNumber1,PhoneNumber2]}">>}; + _ -> + case jsx:is_json(ReqBody) of + false -> + {error, <<"Incorrect Body section. Please, add numbers in format: {\"phone\": [PhoneNumber1,PhoneNumber2]}">>}; + _ -> + PhoneElement = lists:keyfind(<<"phone">>, 1, jsx:decode(ReqBody)), + case lists:keyfind(<<"phone">>, 1, jsx:decode(ReqBody)) of + false -> + {error, <<"Cannot find phones. Please, add numbers in format: {\"phone\": [PhoneNumber1,PhoneNumber2]}">>}; + _ -> + Phones = element(2, PhoneElement), + case is_list(Phones) of + false -> + {error, <<"Incorrect phones data format. Please, add numbers in format: {\"phone\": [PhoneNumber1,PhoneNumber2]}">>}; + _ -> + case Phones of + [] -> + {error, <<"Empty phones list. Please, add numbers in format: {\"phone\": [PhoneNumber1,PhoneNumber2]}">>}; + _ -> + case lists:member(false, lists:flatten([begin is_integer(P) end || P <- Phones])) of + true -> + {error, <<"Incorrect phone format. All phone numbers should be integers">>}; + _ -> + {ok, Phones} + end + end + end + end + end + end. -handle_request('DELETE', "/whitelist", Req) -> - QueryParams = Req:parse_qs(), - roster:info(?MODULE, "~p:DELETE:~p", [Req:get(path), QueryParams]), - PhoneQueryParams = lists:keyfind("phone", 1, QueryParams), - RawRes = case lists:keyfind("phone", 1, QueryParams) of - false -> - [{<<"Status">>, <<"Error">>}, {<<"Reason">>, - <<"Cannot find phones in query params. Please, add it in format: phone=PhoneNumber1,PhoneNumber2">>}]; - _ -> - Whitelist = kvs:all('Whitelist'), - Data = lists:flatten([begin - BinPh = list_to_binary(P), - case lists:keyfind(BinPh, #'Whitelist'.phone, Whitelist) of - false -> - jsx:encode([{<<"Phone">>,BinPh},{<<"Description">>, <<"Cannot find in Whitelist">>}]); - _ -> - kvs:delete('Whitelist', BinPh), - roster:force_logout(BinPh), - jsx:encode([{<<"Phone">>,BinPh},{<<"Description">>, <<"Deleted from Whitelist">>}]) - end - end || P <- string:tokens(element(2, PhoneQueryParams), ",")]), - [{<<"Status">>, <<"Success">>}, {<<"Data">>, Data}] - end, - Res = re:replace(re:replace(re:replace(re:replace(re:replace( - jsx:encode(RawRes), "\\\\\\\"", "\\\"", [{return, list}, global]), - "\\]\\\"", "\\]", [{return, list}, global]), - "\\\"\\[", "\\[", [{return, list}, global]), - "\\\"\\{", "\\{", [{return, list}, global]), - "\\}\\\"", "\\}", [{return, list}, global]), - roster:info(?MODULE, "Res:~p", [Res]), - Req:respond({case string:str(Res, "Error") of 0 -> ?HTTP_CODE_200; _ -> ?HTTP_CODE_400 end, [], Res}); +update_whitelist(Phones) -> + Whitelist = kvs:all('Whitelist'), + lists:foreach(fun(P) -> + BinPh = integer_to_binary(P), + case lists:keyfind(BinPh, #'Whitelist'.phone, Whitelist) of + false -> + kvs:put(#'Whitelist'{phone = BinPh, created = roster:now_msec()}); + _ -> + pass + end + end, Phones). -handle_request(Method, Path, Req) -> - roster:info(?MODULE, "MethodNotAllowed:~p:~p", [Method, Path]), - rest_response_helper:response(Req, ?HTTP_CODE_405, rest_response_helper:error_405()). \ No newline at end of file +delete_whitelist(PhoneNumbers) -> + Whitelist = kvs:all('Whitelist'), + [begin + BinPh = iolist_to_binary(P), + case lists:keyfind(BinPh, #'Whitelist'.phone, Whitelist) of + false -> + [{<<"Phone">>,BinPh},{<<"Description">>, <<"Cannot find in Whitelist">>}]; + _ -> + kvs:delete('Whitelist', BinPh), + roster:force_logout(BinPh), + [{<<"Phone">>,BinPh},{<<"Description">>, <<"Deleted from Whitelist">>}] + end + end || P <- PhoneNumbers]. diff --git a/apps/roster/src/roster.app.src b/apps/roster/src/roster.app.src index e5ed158381e133b9a1950c109e602c6724111cfc..a66e684333888a26fba6e9d9ffc27ba29563f8f7 100644 --- a/apps/roster/src/roster.app.src +++ b/apps/roster/src/roster.app.src @@ -1,7 +1,12 @@ {application, roster, [{description, "Roster Protocol"}, - {vsn, "1"}, + {vsn, "1.0.1"}, {registered, []}, - {applications, [kernel,stdlib,mnesia,kvs,emqttd,n2o,bpe,locus,prometheus,libphonenumber_erlang]}, - {mod, { roster, []}}, + {applications, [kernel,stdlib, mnesia, crypto, inets, ssl, + ibrowse, cowboy, mochiweb, gen_smtp, + kvs, nitro, n2o, emqttc, emqttd, bpe, + jose, jsx, uuid, erlydtl, jwt, + migresia, mini_s3, qdate, rest, enenra, + locus, prometheus, libphonenumber_erlang]}, + {mod, {roster, []}}, {env, []} ]}. diff --git a/apps/roster/src/roster.erl b/apps/roster/src/roster.erl index 0b62d2e39428e6fad2b6f3f62101066770be7952..480009c6ca76d791c70c6d852cf30954d54c1712 100644 --- a/apps/roster/src/roster.erl +++ b/apps/roster/src/roster.erl @@ -2,6 +2,7 @@ -behaviour(application). -compile(export_all). -export([start/2, stop/1, init/1]). +-include_lib("kernel/include/logger.hrl"). -include("roster.hrl"). -include("static_auth.hrl"). -include_lib("n2o/include/n2o.hrl"). @@ -31,12 +32,19 @@ log_modules() -> [ %% 3rd part api amazon_api, vox_api, telesign_api, email_api, google_api, prometheus_api, %% rest api - rest_handler, rest_session, rest_whitelist, rest_whitelist_admin, rest_push, rest_deeplink, rest_helpers, rest_publish, - rest_fake_numbers, rest_metric, rest_cri, rest_users, rest_gw, rest_message, -%% test staff + rest_helpers, + rest_cri, rest_gw, +%% test stuff main_test, auth_test, room_test, -%% other staff - signup, n2o, dashboard, stream, n2o_auth, n2o_vnode, macbert_javascript +%% other stuff + signup, n2o, dashboard, stream, n2o_auth, n2o_vnode, +%% cowboy rest interface + rest_cowboy_room_handler, rest_cowboy_fake_numbers_handler, + rest_cowboy_whitelist_handler, rest_cowboy_handler, + rest_cowboy_chat_handler, rest_cowboy_metric_handler, + rest_cowboy_push_handler, rest_cowboy_cri_handler, + rest_cowboy_session_handler, rest_cowboy_test_handler, + rest_cowboy_users_handler, rest_cowboy_publish_handler ]. register_acl_mod() -> @@ -49,42 +57,46 @@ register_acl_mod() -> atoms() -> [android, ios, contact, signup, signin, welcome]. init([]) -> {ok, {{one_for_one, 5, 10}, []}}. -start(_, _) -> atoms(), catch load([]), - application:stop(n2o), - application:start(n2o), - X = try begin - A = supervisor:start_link({local, roster}, roster, []), - emqttd_access_control:register_mod(auth, n2o_auth, [[]], 9997), - emqttd_access_control:register_mod(auth, roster_auth, [[]], 9998), - emqttd_access_control:register_mod(auth, micro_auth, [[]], 9996), - register_acl_mod(), - % roster_db:restore("mnesia.backup"), - roster_message:start(), - roster_friend:start(), - roster_roster:start(), - roster_profile:start(), - % roster_search:start(), - roster_auth:start(), - micro_auth:start(), - [M:start() || M <- application:get_env(?MODULE, start_modules, [])], - roster_bpe:start(), - rest_handler:init(), - roster_room:start(), - roster_history:start(), - roster_push:start(), - stickers_api:init_default_pack(), - init_default_fake_numbers(), - prometheus_api:init(), - sync_indexes(), - A end - catch E:Z -> - io:format("FATAL ROSTER START: ~p~n",[{E, Z, erlang:get_stacktrace()}]), - [] - end, +start(_, _) -> + atoms(), + try load([]) + catch + Error:Reason -> + ?LOG_WARNING("Error loading ~p: ~p~n",[Error, Reason]) + end, + A = supervisor:start_link({local, roster}, roster, []), + try + emqttd_access_control:register_mod(auth, n2o_auth, [[]], 9997), + emqttd_access_control:register_mod(auth, roster_auth, [[]], 9998), + emqttd_access_control:register_mod(auth, micro_auth, [[]], 9996), + register_acl_mod(), + % roster_db:restore("mnesia.backup"), + roster_message:start(), + roster_friend:start(), + roster_roster:start(), + roster_profile:start(), + % roster_search:start(), + roster_auth:start(), + micro_auth:start(), + [ M:start() || M <- application:get_env(?MODULE, start_modules, [])], + roster_bpe:start(), + rest_cowboy_handler:start(), + roster_room:start(), + roster_history:start(), + roster_push:start(), + stickers_api:init_default_pack(), + init_default_fake_numbers(), + prometheus_api:init(), + sync_indexes() + catch E:Z:ST -> + io:format("FATAL ROSTER START: ~p~n",[{E, Z, ST}]), + erlang:error("FATAL ROSTER START: ~p~n", [{E, Z}]), + {E, Z} + end, google_api:start(), - X. + A. -execution_time(StartTime) -> +execution_time(StartTime) -> TimeInMicroSec = (os:system_time() - StartTime)/1000, list_to_integer(float_to_list(TimeInMicroSec,[{decimals,0}])). @@ -120,24 +132,12 @@ tables() -> [ #table{name = 'ExtendedStar', fields = record_info(fields, 'ExtendedStar')} ]. -%% LOGGER - -info(Module, String, Args) -> roster_io:log(Module, String, Args, info). -info(String, Args) -> roster_io:log(?MODULE, String, Args, info). -info(String) -> roster_io:log(?MODULE, String, [], info). -warning(Module, String, Args) -> roster_io:log(Module, String, Args, warning). -warning(String, Args) -> roster_io:log(?MODULE, String, Args, warning). -warning(String) -> roster_io:log(?MODULE, String, [], warning). -error(Module, String, Args) -> roster_io:log(Module, String, Args, error). -error(String, Args) -> roster_io:log(?MODULE, String, Args, error). -error(String) -> roster_io:log(?MODULE, String, [], error). - % PROFILE MANAGEMENT delete_sessions(Index, P) -> - [begin + [begin n2o_vnode:unsubscribe(ClientId, roster:action_topic(ClientId)), - kvs:delete('Auth', ClientId) + kvs:delete('Auth', ClientId) end || #'Auth'{client_id = ClientId} <- kvs:index('Auth', Index, P)]. delete_sessions({client_id, ClientId}) -> @@ -152,7 +152,7 @@ full_purge_user(Phone) -> purge_user(Phone, full_remove_roster). purge_user(Phone) -> purge_user(Phone, remove_roster). purge_user(Phone, Fun) when is_atom(Fun) -> - info(?MODULE, "purge_user::~p", [Phone]), + ?LOG_INFO("purge_user::~p", [Phone]), R = case kvs:get('Profile', Phone) of {ok, #'Profile'{rosters = Accs}} -> [begin unsubscribe_p2p(Phone, Acc), ?MODULE:Fun(Phone, Acc) end || Acc <- Accs], @@ -166,7 +166,7 @@ list_rosters(Phone) -> list_rosters(Phone, compact_rosters). list_rosters(Phone, Fun) -> case kvs:get('Profile', Phone) of {error, _} -> - roster:error(?MODULE, "~p profile not found", [Phone]), + ?LOG_ERROR("~p profile not found", [Phone]), {error, profile_not_found}; {ok, #'Profile'{rosters = Rosters}} -> ?MODULE:Fun(Phone, Rosters) end. @@ -177,8 +177,8 @@ add_length_settings_maybe(Settings, Size, #'Roster'{userlist = Contacts, [] -> Settings; _ -> RealStars = [RealStar || RealStar = #'Star'{} <- Stars], - info(?MODULE, - "split_roster:TotalLength: ~p Contacts | ~p Rooms | ~p Stars", + ?LOG_INFO( + "split_roster:TotalLength: ~p Contacts | ~p Rooms | ~p Stars", [length(Contacts), length(Rooms), length(RealStars)]), Settings ++ [create_length_feature(contacts, length(Contacts))] @@ -189,16 +189,16 @@ add_length_settings_maybe(Settings, Size, #'Roster'{userlist = Contacts, create_length_feature(Key, Size) -> Type = atom_to_binary(Key, utf8), - #'Feature'{id = [], + #'Feature'{id = [], key = <>, value = list_to_binary(integer_to_list(Size)), group = <<"PAGINATION">>}. send_profile(#'Profile'{rosters = []}, _N, _LastSync, _ClientId, _ClientPid) -> - roster:info(?MODULE, "SEND PROFILE []", []), + ?LOG_INFO("SEND PROFILE []", []), ok; send_profile(#'Profile'{rosters = [[]|TRosterIds]} = Profile, N, LastSync, ClientId, ClientPid) -> - roster:info(?MODULE, "SEND PROFILE []", []), + ?LOG_INFO("SEND PROFILE []", []), send_profile(Profile#'Profile'{rosters = TRosterIds}, N, LastSync, ClientId, ClientPid); send_profile(#'Profile'{ rosters = [RosterId | TRosterIds] = Rosters, @@ -209,10 +209,10 @@ send_profile(#'Profile'{ LastSync, ClientId, ClientPid) -> - roster:info(?MODULE, "SEND PROFILE [~p]", [length(Rosters)]), + ?LOG_INFO("SEND PROFILE [~p]", [length(Rosters)]), case kvs:get('Roster', RosterId) of - {error, _} -> - roster:info(?MODULE, "roster ~p not found in profile ~p", [RosterId, Phone]); + {error, _} -> + ?LOG_INFO("roster ~p not found in profile ~p", [RosterId, Phone]); {ok, #'Roster'{} = Roster} -> ProfileWithNewSettings = Profile#'Profile'{settings = add_length_settings_maybe(Settings, N, Roster)}, NewRoster = Roster#'Roster'{ @@ -244,10 +244,10 @@ send_profile(#'Profile'{ LastSync, ClientId, ClientPid) -> - roster:info(?MODULE, "SEND PROFILE [~p]", [length(Rosters)]), + ?LOG_INFO("SEND PROFILE [~p]", [length(Rosters)]), case kvs:get('Roster', RosterId) of - {error, _} -> - roster:info(?MODULE, "roster ~p not found in profile ~p", [RosterId, Phone]); + {error, _} -> + ?LOG_INFO("roster ~p not found in profile ~p", [RosterId, Phone]); {ok, #'Roster'{} = Roster} -> ProfileWithNewSettings = Profile#'Profile'{settings = add_length_settings_maybe(Settings, N, Roster)}, Fun = fun(Objects) -> @@ -257,7 +257,7 @@ send_profile(#'Profile'{ skip -> ok; NewRoster -> - info(?MODULE, "Sending roster, length ~p", [length(NonEmptyOb)]), + ?LOG_INFO("Sending roster, length ~p", [length(NonEmptyOb)]), roster:send_action(ClientPid, ClientId, ProfileWithNewSettings#'Profile'{ @@ -293,7 +293,7 @@ create_roster([#'ExtendedStar'{} | _] = Stars, Roster) -> roomlist = [], favorite = Stars }; -create_roster([], Roster) -> +create_roster([],_Roster) -> skip. @@ -459,13 +459,14 @@ get_contact(#'Roster'{userlist = Cs}, FriendId) -> get_contact(PhoneId, FriendId) -> get_contact(roster_id(PhoneId), FriendId). to_contact(Rosters, Status) -> to_contact(Rosters, Status, []). -to_contact([#'Roster'{id = Id, names = Name, surnames = SName, nick = Nick, email = EM, +to_contact([#'Roster'{id = Id, names = Name, surnames = SName, nick = Nick, phone = Phone, avatar = A, status = S} | TRosters], Status, Contacts) -> Cs = Contacts ++ [case kvs:get('Profile', Phone) of {ok, #'Profile'{presence = Pr, services = Services, settings = Settings}} -> - Vox = case Services of [#'Service'{type = vox, login = V} | _] -> V;_ -> [] end, - PhoneId = phone_id(Phone, Id), - ContactSettings = lists:flatten(Settings ++ [roster_db:new_feature(PhoneId, account_id)]), + %% TODO: This looks like a bug. Why is Vox unused? + _Vox = case Services of [#'Service'{type = vox, login = V} | _] -> V;_ -> [] end, + PhoneId = phone_id(Phone, Id), + ContactSettings = lists:flatten(Settings ++ [roster_db:new_feature(PhoneId, account_id)]), #'Contact'{phone_id = PhoneId, names = Name, services = Services, nick = Nick, presence = Pr, settings = ContactSettings, surnames = SName, avatar = A, @@ -485,9 +486,7 @@ add_contacts(#'Roster'{userlist=List}=Roster, Contacts) -> false -> [Phone, _] = parts_phone_id(PhoneId), - [Phone,_]=parts_phone_id(PhoneId), - - info(?MODULE, "ContactServices:~p",[{PhoneId,Phone}]), + ?LOG_INFO("ContactServices:~p",[{PhoneId,Phone}]), %% {ok, #'Profile'{services = Services}} = kvs:get('Phone', Phone), {A, [C#'Contact'{presence=[], services = get_services(Phone)}|L]}; _F -> {[PhoneId|A],L} end end,{[],List},Contacts), @@ -571,19 +570,19 @@ unload() -> emqttd:unhook('client.disconnected', fun ?MODULE:on_client_disconnected/3). on_client_disconnected(_Reason, #mqtt_client{client_id = <<"emqttd_", _/bytes>> = ClientId} = Client, _Env) -> - info(roster_auth, "~p:MQTT_CLIENT DISCONNECT ~p",[ClientId, Client]), + ?LOG_INFO("~p:MQTT_CLIENT DISCONNECT ~p",[ClientId, Client]), % TODO: mqttc:publish(C, lists:concat(["ses/",Phone])) on_disconnect(ClientId); on_client_disconnected(_Reason, #mqtt_client{client_id = <<"reg_", _/bytes>> = ClientId} = Client, _) -> - info(roster_auth, "~p:MQTT_CLIENT DISCONNECT ~p",[ClientId, Client]), + ?LOG_INFO("~p:MQTT_CLIENT DISCONNECT ~p",[ClientId, Client]), final_disconnect(ClientId); on_client_disconnected(_Reason, #mqtt_client{client_id = ClientId} = Client, _Env) -> - info(roster_auth, "~p:MQTT_CLIENT DISCONNECT ~p",[ClientId, Client]), + ?LOG_INFO("~p:MQTT_CLIENT DISCONNECT ~p",[ClientId, Client]), ok. on_disconnect(<<"emqttd_", _/binary>> = ClientId) -> - info(roster_auth, "~p:CLIENT DISCONNECT",[ClientId]), + ?LOG_INFO("~p:CLIENT DISCONNECT",[ClientId]), case kvs:get('Auth', ClientId) of {ok, Auth} -> n2o_async:pid(system, roster_auth) ! {disconnect, Auth#'Auth'{type = disconnect}}, @@ -592,7 +591,7 @@ on_disconnect(<<"emqttd_", _/binary>> = ClientId) -> end, ok. final_disconnect(ClientId) -> - % info(roster_auth, "~p:ANY DISCONNECT",[RegClientId]), + % ?LOG_INFO("~p:ANY DISCONNECT",[RegClientId]), n2o_vnode:unsubscribe(ClientId, roster:action_topic(ClientId)), case kvs:get(mqtt_session, ClientId) of {ok, #mqtt_session{sess_pid = SessionPid}} -> @@ -629,7 +628,7 @@ send_event(C, ClientId, Token, Term, {K,V}) -> send_event(C, ClientId, Token, Term, VNode) -> n2o_vnode:send(C, event_topic(ClientId, Token, VNode), term_to_binary(Term)). send_action(C, ClientId, Term) -> send_action(C, ClientId, Term, "1"). -send_action(C, ClientId, <<>>, _) -> <<>>; +send_action(_C,_ClientId, <<>>, _) -> <<>>; send_action(C, ClientId, Term, VSN) -> send(C, action_topic(ClientId, VSN), Term). @@ -674,7 +673,7 @@ update_writer(Feed,{E1,V1},{E2,V2})-> kvs_stream:save(W#writer{cache = M1}); false -> skip end; _ -> skip end. -update_chains({Table, Index},{E1,V1},{E2,V2}) -> +update_chains({Table, Index},{_E1,V1},{E2,V2}) -> [kvs:put(erlang:setelement(E2,M,V2)) || M <- kvs:index(Table, Index,V1)], ok. %%update_chain({, {Table, Index},{E1,V1},{E2,V2}) -> @@ -692,11 +691,14 @@ del_jobs(Name, PhoneId) -> ok. add_message(#'Message'{feed_id = Feed} = Msg) -> - kvs_stream:save(kvs_stream:add( - (kvs_stream:load_writer(Feed))#writer{args = - desc_id(Msg#'Message'{created = now_msec()})})). + Writer = kvs_stream:load_writer(Feed), + DescId = desc_id(Msg#'Message'{created = now_msec()}), + kvs_stream:save(kvs_stream:add(Writer#writer{ args = DescId })). + add_message(#'Message'{} = Msg, Reader) -> - kvs_stream:save(offset_reader(#writer{cache = LMsg} = add_message(Msg), Reader)), LMsg. + Writer = add_message(Msg), + kvs_stream:save(offset_reader(Writer, Reader)), + Writer#writer.cache. -spec unread_msg(#muc{} |#p2p{} |#writer{} |#error{}, integer() | #reader{}, @@ -709,7 +711,7 @@ unread_msg(#writer{cache = #'Message'{id = MaxReadId, type = [sys|_]} = LastMsg} #'Member'{id = UID, status = removed}) when ReadMsgId /= [], ReadMsgId >= MaxReadId -> GetFun = - fun (_T, MaxReadId, _D) -> {ok, LastMsg#'Message'{seenby = []}}; + fun (_T, Id, _D) when Id =:= MaxReadId -> {ok, LastMsg#'Message'{seenby = []}}; (T, Id, D) -> kvs:get(T, Id, D) end, unread_msg(Writer#writer{cache = LastMsg}, Reader#reader{cache = {'Message', MaxReadId}}, UID, GetFun); @@ -738,9 +740,23 @@ unread_msg(Feed, #reader{} = Reader, UID) when is_record(Feed, muc); is_record(F unread_msg(kvs_stream:load_writer(Feed), Reader, UID); unread_msg(_F, [], _U) -> {0, [], []}; unread_msg(F, R, U) -> - info(?MODULE, "unread_msg2: unexpected data format: ~p", [{F, R, U}]), + ?LOG_INFO("unread_msg2: unexpected data format: ~p", [{F, R, U}]), {0, [], []}. +%% Counting unread messages in feed +%% Writer has MaxReadId (last message written) and Count = number of messages +%% Reader has ReadMsgId (possibly []) and ReadCurPos. +%% +%% We traverse the message chain starting from MaxReadId. +%% We increment Unread if: +%% - the message is an active message (not edit/delete) AND +%% - MsgId > ReadMsgId OR ReadMsgId == [] +%% +%% We stop if: +%% - Pos (in reader) becomes negative OR +%% - MsgId =< ReadMsgId AND ReadCurPos >= Pos. +%% +%% ReadCurPos is always >= Pos so that condition isn't needed. unread_msg(#writer{cache = #'Message'{id = MaxReadId}, count = Count}, #reader{cache = {'Message', ReadMsgId}, pos = ReadCurPos} = Reader, UID, GetFun) -> AccFun = @@ -792,7 +808,7 @@ unread_msg(#writer{cache = #'Message'{id = MaxReadId}, count = Count}, link_msg(#'Message'{feed_id = Feed, type = [reply|_], link = LinkId}, UID) when is_integer(LinkId) -> case kvs:get('Message', LinkId) of {error, not_found} -> - roster:info(?MODULE, "link ~p for reply not found", [LinkId]), + ?LOG_INFO("link ~p for reply not found", [LinkId]), #error{code = invalid_data}; {ok, #'Message'{feed_id = Feed, status = [], type = T} = LnkMsg} when T == []; hd(T) /= sys -> @@ -886,7 +902,7 @@ gen_token(Token, Data) -> Now = now_msec(), case depicle(Token) of <<>> -> {error, invalid_token}; {Expiration, _} when Expiration > Now -> {'Token', Token}; - {Expiration, _} -> gen_token([], Data) + {_Expiration, _} -> gen_token([], Data) end. gen_token2(Token, Data) -> case gen_token(Token, Data) of @@ -897,14 +913,14 @@ parse_token(Token) -> case depicle(Token) of <<>> -> {error, invalid_token}; - {Expiration, D} when Expiration =< Now -> + {Expiration,_D} when Expiration =< Now -> {error, token_expired}; - {_, D} -> D + {_, D} -> D end. parse_token2(Token) -> case depicle(Token) of <<>> -> {error, invalid_token}; - {_, D} -> D + {_, D} -> D end. get_vnode(Term) -> [H|_] = binary_to_list(erlang:md5(term_to_binary(Term))), @@ -928,14 +944,14 @@ validate(#'Message'{mentioned = [_|_] = Mentioned} = Msg) -> validate(#'Message'{status = Status, files = [_|_] = Descs, type = []} = Msg) when Status == []; Status == edit -> case roster:verify_thumbs(Descs) of {[], []} -> validate(Msg#'Message'{files = []}); - _ -> roster:error(?MODULE, "invalid thumb or media desc: ~p", [Msg]), [{files, Msg}] + _ -> ?LOG_ERROR("invalid thumb or media desc: ~p", [Msg]), [{files, Msg}] end; validate(#'Message'{status = [], type = [sys|_]} = Msg) -> - info(?MODULE, "invalid sys type: ~p", [Msg]), [{type, Msg}]; + ?LOG_INFO("invalid sys type: ~p", [Msg]), [{type, Msg}]; validate(#'Message'{status = [], type = [_|_] = Types} = Msg) -> case [T||T<-Types, not is_atom(T)] of [] -> []; - _ -> info(?MODULE, "invalid message type: ~p", [Msg]), [{type, Msg}] + _ -> ?LOG_INFO("invalid message type: ~p", [Msg]), [{type, Msg}] end; validate(#'Desc'{id = <<>>} = Desc) -> [{id, Desc}]; validate(_) -> []. @@ -948,7 +964,7 @@ validate(Payload, ClientId) -> Term = prevalidate(binary_to_term(Payload)), case roster_validator:validate(Term) of [] -> ok; - Res -> info(?MODULE, "validate error:~n~p", [Res]), + Res -> ?LOG_INFO("validate error:~n~p", [Res]), emqttd:publish(emqttd_message:make( ClientId, 2, action_topic(ClientId), term_to_binary(#errors{code = [<<"666">>], data = Res}))), error @@ -1039,36 +1055,48 @@ member_lastmsg2(PhoneId, Room) -> R -> R end. add_member(#'Room'{} = R, #'Member'{} = M) -> add_member(R, M, {no_muc_message, []}). -add_member(#'Room'{} = R, #'Member'{id = MId, reader = 0, feed_id = Feed, phone_id = PhoneId, alias = Alias} = M, - {MsgPayload, HL} = Args) -> +add_member(#'Room'{} = R, #'Member'{id = MId, reader = 0, feed_id = Feed, phone_id = PhoneId, alias = Alias} = M, Args) -> case kvs:get('Roster', roster_id(PhoneId)) of {ok, #'Roster'{nick = Nick, names = Names, surnames = Surnames, avatar = Avatar}} -> Alias2 = case Alias of EmptyAlias when EmptyAlias == []; EmptyAlias == <<>> -> Nick; _ -> Alias end, - Rdr = #reader{id = NewR} = kvs_stream:reader(Feed), %% Get reader, cached to first msg in feed - HistLimit = case HL of [] -> 0; [H | _] -> H-1 end, - Rdr2 = find_bot(kvs_stream:load_writer(Feed), Rdr, MId, HistLimit), %% Move reader, to history-limit - Reader = kvs_stream:save(Rdr2), + #reader{id = NewR} = kvs_stream:save(kvs_stream:reader(Feed)), %% Get reader, cached to first msg in feed {NewMId, AddFun} = case MId of [] -> {kvs:next_id('Member', 1), add};_ -> {MId, put} end, {ok, #'Profile'{settings = Settings}} = kvs:get('Profile', roster:phone(PhoneId)), - {M2, Msg, R2} = add_member(R, M#'Member'{id = NewMId, reader = NewR, container = chain, feed_id = Feed, + {M2, Msg, Room} = add_member(R, M#'Member'{id = NewMId, reader = NewR, container = chain, feed_id = Feed, alias = Alias2, names = Names, surnames = Surnames, avatar = Avatar, settings = Settings}, Args), kvs:AddFun(M2), - Room = case MsgPayload of no_muc_message -> R2; - _ -> {_, R3} = put_readers(write_top, M#'Member'{reader = Reader}, R), R3 end, {M2, Msg, Room}; #error{} -> {error, roster_not_found} %% TODO add error handler end; -add_member(#'Room'{} = R, #'Member'{feed_id = #muc{name = Room} = Feed, alias = Alias, - phone_id = Roster = #'Roster'{id = RosterId, phone = Phone, - names = Name, surnames = Surname}} = M, {MsgPayload, _}) -> +add_member(#'Room'{} = R, + #'Member'{id = MemberId, feed_id = #muc{name = Room} = Feed, reader = ReaderId, alias = Alias, phone_id = Roster } = Member, + {MsgPayload, HL}) when is_record(Roster, 'Roster') -> + #'Roster'{id = RosterId, phone = Phone, names = Name, surnames = Surname} = Roster, PhoneId = phone_id(Phone, RosterId), + %% Update reader to new history limit + HistLimit = case HL of [] -> 0; [H | _] -> H - 1 end, + Writer = kvs_stream:load_writer(Feed), + {ok, Reader0} = kvs:get(reader, ReaderId), + LastRead = case Reader0 of + #reader{ pos = Pos, cache = {'Message', MsgId} } when Pos > 0 -> MsgId; + _ -> [] + end, + Reader = find_bot(Writer, Reader0, MemberId, LastRead, HistLimit), + kvs:put(Reader), Msg = case MsgPayload of no_muc_message -> []; - _ -> #writer{cache = LastMsg} = add_message(#'Message'{status = [], type = [sys], feed_id = Feed, from = PhoneId, to = Room, - files = [#'Desc'{payload = MsgPayload}]}), LastMsg end, - update_rooms(Roster, R), subscribe_room(M2 = M#'Member'{phone_id = PhoneId}), + _ -> + MucMsg = #'Message'{status = [], type = [sys], feed_id = Feed, + from = PhoneId, to = Room, + files = [#'Desc'{payload = MsgPayload}]}, + #writer{cache = LastMsg} = add_message(MucMsg), + LastMsg + end, + Member2 = Member#'Member'{phone_id = PhoneId, alias = member_alias(Alias, Name, Surname)}, + update_rooms(Roster, R), + subscribe_room(Member2), subscribe_muc(PhoneId, Feed), - {M2#'Member'{alias = member_alias(Alias, Name, Surname)}, Msg, R}; + {Member2, Msg, R}; add_member(#'Room'{} = R, #'Member'{phone_id = PhoneId} = M, Args) -> case kvs:get('Roster', roster_id(PhoneId)) of {ok, Roster} -> add_member(R, M#'Member'{phone_id = Roster}, Args); @@ -1078,58 +1106,52 @@ member_alias([], [], []) -> []; member_alias([], N, []) -> N; member_alias([], [ member_alias([], Name, Surname) when Name /= [], Surname /= [] -> <>/binary, Surname/binary>>; member_alias(Alias, _, _) -> Alias. -put_readers(_TopAction, _Memeber, #'Room'{type = channel} = Room) -> {[], Room}; -put_readers(TopAction, #'Member'{reader = ReaderId} = M, #reader{id = ReaderId} = Reader) when is_integer(ReaderId) -> - put_readers(TopAction, M#'Member'{reader = Reader}); -put_readers(_, #'Member'{reader = #reader{cache = []}}, #'Room'{} = Room) -> {[], Room}; -put_readers(TopAction, #'Member'{reader = ReaderId} = M, Room) when is_integer(ReaderId) -> - put_readers(TopAction, M#'Member'{reader = kvs_stream:load_reader(ReaderId)}, Room); -put_readers(TopAction, #'Member'{id = Id, feed_id = #muc{name = RoomId}, phone_id = PhoneId, - reader = #reader{cache = {'Message', MsgId}}}, #'Room'{id = RoomId, readers = Readers} = Room) - when TopAction == write_top; TopAction == read_top -> - Readers2 = - case {Readers, TopAction} of - {[Id | _], _} -> Readers; - {[], _} -> [Id]; - {[_ | _], write_top} -> lists:sublist([Id | Readers--[Id]], 2); - {[_], read_top} -> Readers ++ [Id]; - {[_, Id], read_top} -> Readers; - {[WMemberId, 0], read_top} -> [WMemberId, Id]; - {[WMemberId, RMemberId], read_top} -> - case kvs:get('Member', RMemberId) of - {ok, #'Member'{reader = RId}} -> - case kvs_stream:load_reader(RId) of - #reader{cache = {'Message', RMsgId}} when MsgId > RMsgId -> - [WMemberId, Id]; - _ -> Readers end; _ -> Readers end; - Res -> info(roster_auth, "invalid put_readers:~p", [Res]), Readers +put_readers(_Member, #'Room'{type = channel} = Room) -> {[], Room}; +put_readers(#'Member'{reader = #reader{cache = []}}, + #'Room'{} = Room) -> + {[], Room}; +put_readers(#'Member'{reader = #reader{cache = {'Message', MsgId}}, + feed_id = #muc{}, + phone_id = PhoneId}, + #'Room'{readers = Readers} = Room) -> + Max = fun([], Id) -> Id; + (Id, []) -> Id; + (Id1, Id2) -> max(Id1, Id2) + end, + NewReaders = + case Readers of + none -> {last_read, MsgId}; + {last_read, MsgId1} -> {last_read, Max(MsgId1, MsgId)}; + %% Deal with old representation + [] -> {last_read, MsgId}; + [_W] -> {last_read, MsgId}; + [_W, RMemId] -> + try + {ok, #'Member'{reader = RId}} = kvs:get('Member', RMemId), + #reader{cache = {'Message', MsgId1}} = kvs_stream:load_reader(RId), + {last_read, Max(MsgId1, MsgId)} + catch _:_ -> + {last_read, MsgId} + end end, - Room2 = case Readers2 of Readers -> Room;_ -> - kvs:put(R2 = Room#'Room'{readers = Readers2}), R2 end, - {case {TopAction, lists:member(Id, Readers2)} of - {read_top, true} -> + case NewReaders == Readers of + true -> {[], Room}; + false -> + NewRoom = Room#'Room'{readers = NewReaders}, + kvs:put(NewRoom), {ok, Msg} = kvs:get('Message', MsgId), - Msg#'Message'{type = [read], files = [], from = PhoneId}; - _ -> [] end, Room2}; - -put_readers(read_top, #'Contact'{phone_id = PhoneId}, #reader{cache = {'Message', MsgId}}) -> + {Msg#'Message'{type = [read], files = [], from = PhoneId}, NewRoom} + end; +put_readers(#'Member'{reader = ReaderId, feed_id = #muc{name = R}} = M, + #reader{id = ReaderId} = Reader) when is_integer(ReaderId) -> + {ok, Room} = kvs:get('Room', R), + put_readers(M#'Member'{reader = Reader}, Room); +put_readers(#'Contact'{phone_id = PhoneId}, #reader{cache = {'Message', MsgId}}) -> {ok, Msg} = kvs:get('Message', MsgId), {Msg#'Message'{type = [read], files = [], from = PhoneId}, []}; -put_readers(_TopAction, #'Contact'{} = _C, _Reader) -> {[], []}; -put_readers(T, M, R) -> - info(roster_auth, "invalid put_readers: ~p", [{T, M, R}]), {[], []}. - -put_readers(TopAction, #'Member'{feed_id = #muc{name = R}} = M) -> - {ok, Room} = kvs:get('Room', R), - put_readers(TopAction, M, Room). - -sort_readers(#'Room'{id = Room, type = group} = R) -> - {MembIds, _} = lists:unzip(lists:sublist(lists:reverse(lists:keysort(2, - [{MembId, get_reader(Member)} || #'Member'{id = MembId} = Member<-members(#muc{name = Room})])), 2)), - R#'Room'{readers = MembIds}; -sort_readers(#'Room'{} = R) -> R; -sort_readers(Room) -> - case kvs:get('Room', Room) of {ok, R} -> sort_readers(R); Err -> Err end. +put_readers(#'Contact'{} = _C, _Reader) -> {[], []}; +put_readers(M, R) -> + ?LOG_INFO("invalid put_readers: ~p", [{M, R}]), {[], []}. readmsgs(#'Contact'{phone_id = R}=C, LocR) -> C#'Contact'{reader = p2p_readmsgs(LocR, R)}; readmsgs(M, _) -> M. @@ -1153,7 +1175,7 @@ contact_readmsg(LocalPhoneId, PhoneId) -> {ok, #'Roster'{} = R}-> contact_readmsg(LocalPhoneId, R); _ -> 0 end. %% TODO need refactoring. Reader may be found by feed? -update_room(#'Roster'{roomlist = Rooms} = Roster, #'Room'{status = Status} = Room)-> +update_room(#'Roster'{} = Roster, #'Room'{} = Room)-> kvs:put(Roster#'Roster'{roomlist = lists:keyreplace(#'Room'.id, Room)}). update_rooms(#'Roster'{roomlist = Rooms} = Roster, #'Room'{status = Status} = R) -> @@ -1239,11 +1261,11 @@ last_objs([Obj|TObjs], PhoneId, LastSync, N, {Acc, MaxUpd}) -> {0, Feed} end, LastUpd = erlang:max(Created, last_upd(Obj)), - Acc2 = case LastUpd > LastSync of + Acc2 = case LastUpd > LastSync of true -> Acc++[{Obj, W}]; _ -> - Acc + Acc end, %% TODO sort objects if update? last_objs(TObjs, PhoneId, LastSync, N, {Acc2, erlang:max(MaxUpd, LastUpd)}). @@ -1262,8 +1284,8 @@ split_objlist(Index, #'Roster'{} = Roster, LastSync, N, Acc) split_objlist(element(Index, Roster), Roster, LastSync, N, Acc); split_objlist([], _Roster, _LastSync, _N, Acc) -> Acc; -split_objlist([_|_] = List, #'Roster'{}=Roster, LastSync, N, Acc) -> - %[ Objs | TObjs ] = +split_objlist([_|_] = List, #'Roster'{}=Roster, LastSync, N,_Acc) -> + %[ Objs | TObjs ] = objlist(List, Roster, LastSync, N); %split_objlist(TObjs, Roster, LastSync, N, Acc ++ [Objs]); split_objlist(Index, Id, LastSync, N, Acc) @@ -1274,8 +1296,8 @@ split_objlist(Index, Id, LastSync, N, Acc) {ok, R} -> split_objlist(Index, R, LastSync, N, Acc); _ -> - info(?MODULE, "roster ~p not found", [Id]), - {error, roster_not_found} + ?LOG_INFO("roster ~p not found", [Id]), + {error, roster_not_found} end. objlist(Index, Roster) -> @@ -1306,18 +1328,18 @@ objlist([Object | _] = Objects, end, [[]], Objects), - info(?MODULE, "Objlist args ~p ~p ~p", [LastSync, N, length(hd(Objs))]), + ?LOG_INFO("Objlist args ~p ~p ~p", [LastSync, N, length(hd(Objs))]), case Objs of [[] | TObjs] -> TObjs; [H | _ ] when length(H) =< N -> Fun(H), Objs; - _ -> + _ -> Objs end; objlist([_Obj | TObjs], #'Roster'{id = _Id} = Roster, LastSync, N, Fun) -> -% roster:info(?MODULE, "invalid object in roster ~p: ~p", [Id, Obj]), +% ?LOG_INFO("invalid object in roster ~p: ~p", [Id, Obj]), objlist(TObjs, #'Roster'{} = Roster, LastSync, N, Fun); objlist(Index, Id, LastSync, N, Fun) when Index == #'Roster'.userlist; Index == #'Roster'.roomlist; @@ -1349,7 +1371,7 @@ build_object(Object, Acc, maybe_build_and_add_object(Object, Acc, Roster, Feed, LastUpd, LastSync) end. -maybe_build_and_add_object(_Object, Acc, _Roster, _Writer, LastUpd, LastSync) +maybe_build_and_add_object(_Object, Acc, _Roster, _Writer, LastUpd, LastSync) when LastUpd =< LastSync -> Acc; maybe_build_and_add_object(#'Contact'{} = Object, [HAcc | TAcc], Roster, Writer, _LastUpd, LastSync) -> @@ -1387,7 +1409,7 @@ user(#'Roster'{ id = Id, phone = Phone } = Roster, {Presence, Update} = is_online2(PhoneId), Msgs = case catch p2p_readmsgs(Roster, C) of {'EXIT', {Err, _}} -> - n2o:error(?MODULE,"Catch:~p~n",[n2o:stack_trace(error, Err)]), + ?LOG_ERROR("Catch:~p~n",[n2o:stack_trace(error, Err)]), throw({error, {user, LocalPhoneId, PhoneId}}); Ms -> Ms end, @@ -1406,15 +1428,15 @@ user(#'Roster'{ id = Id, phone = Phone } = Roster, reader = Msgs, settings = Settings }; -user(#'Roster'{id = RosterId, userlist = Users, phone = Phone}=R, UserId, W, LastSync) -> +user(#'Roster'{id = RosterId, userlist = Users}=R, UserId, W, LastSync) -> case lists:keyfind(UserId, #'Contact'.phone_id, Users) of #'Contact'{} = C -> user(R, C, W, LastSync); - false -> info(?MODULE, "user ~p not found in list for users~p", [UserId, RosterId]), [] end; + false -> ?LOG_INFO("user ~p not found in list for users~p", [UserId, RosterId]), [] end; user(RosterId, UserId, W, LastSync) -> %% get Contact object from userlist, RosterId - acted user Roster.id, UserId - friend phone_id case kvs:get('Roster', RosterId) of {ok, #'Roster'{} = R} -> user(R, UserId, W, LastSync); - {error, _} -> info(?MODULE, "roster ~p not found", [RosterId]), [] end. + {error, _} -> ?LOG_INFO("roster ~p not found", [RosterId]), [] end. roomlist(Id) when is_integer(Id) -> roomlist(Id, []). @@ -1426,7 +1448,7 @@ roomlist(Id, N) -> case kvs:get('Roster', Id) of {ok, R} -> roomlist(R, N); _ -> room(Roster, Room) -> room(Roster, Room, [], 0). room(#'Roster'{} = Roster, #'Room'{id = Name} = Room, [], LastSync) -> room(Roster, Room, kvs_stream:load_writer(#muc{name = Name}), LastSync); -room(#'Roster'{id = Id, phone = Phone}, #'Room'{id = Name, last_msg = LMId, type = Type} = Room, +room(#'Roster'{id = Id, phone = Phone}, #'Room'{id = Name, last_msg = LMId} = Room, #writer{} = W, _LastSync) -> Local = phone_id(Phone, Id), case muc_member(Local, Name) of @@ -1440,7 +1462,7 @@ room(#'Roster'{id = Id, phone = Phone}, #'Room'{id = Name, last_msg = LMId, type {Unread, LastMsg, Mentions} = case unread_msg(Rec, Reader, Member) of {_, #'Message'{}, _} = Res -> Res; - Res -> {0,[],[]} + _Res -> {0,[],[]} end, %% {Unread, LastMsg = #'Message'{from = PMention, mentioned = Mentioned}, Mentions} @@ -1469,12 +1491,12 @@ room(#'Roster'{id = Id, phone = Phone}, #'Room'{id = Name, last_msg = LMId, type room(#'Roster'{id = RosterId, roomlist = Rooms} = R, RoomId, W, LastSync) -> case lists:keyfind(RoomId, #'Room'.id, Rooms) of #'Room'{} = Room -> room(R, Room, W, LastSync); - false -> info(?MODULE, "room ~p not found in roomlist ~p", [RoomId, RosterId]), [] end; + false -> ?LOG_INFO("room ~p not found in roomlist ~p", [RoomId, RosterId]), [] end; room(RosterId, RoomId, W, LastSync) -> %% get Room object from all rooms, RosterId - acted user, RoomId - room to be found case kvs:get('Roster', RosterId) of {ok, #'Roster'{} = R} -> room(R, RoomId, W, LastSync); - {error, _} -> info(?MODULE, "roster ~p not found", [RosterId]), [] end. + {error, _} -> ?LOG_INFO("roster ~p not found", [RosterId]), [] end. % STARRED MESSAGES MANAGEMENT @@ -1542,12 +1564,12 @@ roster(#'Roster'{} = Roster, N, LastSync) -> favorite = objlist(#'Roster'.favorite, Roster, LastSync, N) }; -roster(Id, N, LastSync) -> +roster(Id, N, LastSync) -> case kvs:get('Roster', Id) of {ok, Roster} -> roster(Roster, N, LastSync); _ -> - [] + [] end. sub_room(subscribe, #'Member'{status = removed}) -> []; @@ -1622,7 +1644,7 @@ is_shared_rooms_([#'Room'{id = Id, status = S1} | _], true; is_shared_rooms_([#'Room'{id = Id1} | Rooms1], [#'Room'{id = Id2} | _] = Rooms2) when Id1 < Id2 -> is_shared_rooms_(Rooms1, Rooms2); -is_shared_rooms_([#'Room'{id = Id1} | _] = Rooms1, [#'Room'{id = Id2} | Rooms2]) -> +is_shared_rooms_([#'Room'{} | _] = Rooms1, [#'Room'{} | Rooms2]) -> is_shared_rooms_(Rooms1, Rooms2). patch_room(#'Room'{update = Upd} = OldR, #'Room'{settings = Settings} = NewR) when Settings /= [] -> @@ -1639,7 +1661,7 @@ patch_room(#'Room'{} = OldR, #'Room'{data = Descs} = NewR) -> patch_member(OldM, NewM) -> patch_member(OldM, NewM, [id, container, feed_id, prev, next, feeds, phone_id, presence, reader, status]). -patch_member(#'Member'{phone_id = PhoneId, feed_id = Feed}=OldM, #'Member'{alias = Alias} = NewM, ExcludeFields) -> +patch_member(#'Member'{}=OldM, #'Member'{alias = Alias} = NewM, ExcludeFields) -> M = patch_record(ExcludeFields, OldM, NewM#'Member'{alias = Alias}, record_info(fields, 'Member')), M#'Member'{update = now_msec()}. @@ -1655,9 +1677,20 @@ split_members(Members) -> reader_cache(ReaderId) when is_integer(ReaderId)-> reader_cache(kvs_stream:load_reader(ReaderId)); reader_cache(#reader{pos = 0}) -> 0; reader_cache(#reader{cache = {'Message', MsgId}}) -> MsgId; -reader_cache(#'Room'{admins = Admins, members = Members, readers = MembIds} = R) -> - R#'Room'{admins = reader_cache(Admins), members = reader_cache(Members), - readers = lists:sublist(reader_cache(MembIds)++[0,0], 2)}; +reader_cache(#'Room'{admins = Admins, id = RoomId, members = Members, readers = Readers} = R) -> + LastMsg = case kvs:get(writer, #muc{name = RoomId}) of + {ok, #writer{cache = #'Message'{id = MId}}} -> MId; + _ -> 0 + end, + Readers1 = + case Readers of + none -> [LastMsg, 0]; + {last_read, MsgId} -> [LastMsg, MsgId]; + MembIds -> lists:sublist(reader_cache(MembIds) ++ [0, 0], 2) + end, + R#'Room'{admins = reader_cache(Admins), + members = reader_cache(Members), + readers = Readers1}; reader_cache(#'Member'{reader = 0} = Member) -> Member; reader_cache(#'Member'{reader = ReaderId}=Member) -> Member#'Member'{reader = reader_cache(ReaderId)}; @@ -1699,31 +1732,42 @@ status_msg(T, UID, FS) -> LStatus = [[], clear], true -> erlang:setelement(FS, T, update); R -> R end; _ -> false end. -find_bot(#writer{cache = []}, #reader{} = Reader, _UID, _HistLimit) -> Reader; -find_bot(#writer{count = Count}, #reader{} = Reader, _UID, HistLimit) when HistLimit >= Count -> - Reader#reader{pos = 0, cache = []}; -find_bot(#writer{cache = #'Message'{id = LastMsgId}}, Reader, _UID, 0) -> - Reader#reader{pos = 0, cache = {'Message', LastMsgId}}; -find_bot(#writer{count = Count, cache = #'Message'{id = LastMsgId}}, #reader{} = Reader, UID, HistLimit) -> - StartReader = Reader#reader{pos = Count, cache = {'Message', LastMsgId}}, - AccFun = fun%%(_, #'Message'{type = [sys|_]}, C, _Dir) -> C; - (1, _Msg, C, _Dir) when C > 0 -> C-1; - (_, _, Acc, _) -> Acc - end, - - FilterFun = roster:msg_filter_fun(-Count, fun msg_filter/2, UID, AccFun), - FilterFun2 = fun(Msg, {C, _} = Acc) when C > 0 -> FilterFun(Msg, Acc); - (_Msg, Acc) -> Acc - end, - - StopFun = roster:msg_stop_fun([], UID), - StopFun2 = fun(_Msg, {C, #reader{}}) when C =< 0 -> 0; - (Msg, A) -> StopFun(Msg, A) - end, - - {_, Rdr} = roster:fold(FilterFun2, {HistLimit, StartReader}, 'Message', LastMsgId, [], - #kvs{mod = store_mnesia}, #iterator.next, StopFun2), - norm_reader(Rdr#reader{pos = 0}). +find_bot(#writer{cache = []}, #reader{} = Reader, _UID, _StopMsg, _HistLimit) -> Reader; +find_bot(#writer{count = Count, cache = #'Message'{id = LastMsgId}}, #reader{} = Reader, UID, StopMsg, HistLimit) -> + Fun = fun(#'Message'{id = MsgId} = Msg, {Pos, HL, NewPos}) -> + HL1 = HL - msg_filter(Msg, UID), + if Pos == 0; HL == 0 -> {stop, {if NewPos > 0 -> StopMsg; + true -> MsgId + end, NewPos}}; + MsgId == StopMsg -> {cont, {Pos - 1, HL1, 1}}; + NewPos > 0 -> {cont, {Pos - 1, HL1, NewPos + 1}}; + true -> {cont, {Pos - 1, HL1, 0}} + end end, + {Cache, Pos} = + case fold_stream('Message', Fun, LastMsgId, {Count, HistLimit, 0}, bwd) of + {eos, {_, _, 0}} -> {[], 0}; + {eos, {_, _, NewPos}} -> {{'Message', StopMsg}, NewPos}; + {ok, {MId, P}} -> {{'Message', MId}, P} + end, + Reader#reader{pos = Pos, cache = Cache}. + +%% Check if a message is within the history limit of the given reader. +message_within_history_limit(MsgId, Reader) -> + case Reader of + #reader{cache = []} -> true; + #reader{cache = {'Message', ReadMsg}, pos = P} when P > 0, MsgId >= ReadMsg -> true; + #reader{cache = {'Message', ReadMsg}, pos = 0} when MsgId > ReadMsg -> true; + #reader{cache = {'Message', ReadMsg}, pos = Pos} -> + Fun = fun(#'Message'{id = MsgId1}, P) -> + if P == 0 -> {stop, false}; %% Reached the limit + MsgId1 =< MsgId -> {stop, true}; %% Found the msg within the limit + true -> {cont, P - 1} + end end, + case fold_stream('Message', Fun, ReadMsg, Pos, bwd) of + {eos, _} -> false; + {ok, InLimit} -> InLimit + end + end. start_reader(#reader{cache = []} = Reader) -> Reader#reader{cache = {'Message', []}}; start_reader(Reader) -> Reader. @@ -1786,6 +1830,23 @@ msg_filter_fun(DirSize, InnerFilterFun, UID, AccFun) -> Rdr#reader{cache = {'Message', element(Iter, Msg)}, pos = Pos + Sign}; _ -> Rdr end}; (_, A) -> A end. +msg_filter_fun2(DirSize, InnerFilterFun, UID, AccFun) -> + {Iter, Sign} = case DirSize > 0 of true -> {#iterator.prev, 1}; _ -> {#iterator.next, -1} end, + fun(#'Message'{id = Id} = Msg, {Acc, #reader{cache = {'Message', LastReadId}, pos = Pos} = Rdr}) -> + %% Run AccFun if not yet caught up with reader or reader pos > 0 + Acc1 = case Id /= LastReadId orelse Pos > 0 of + true -> AccFun(InnerFilterFun(Msg, UID), Msg, Acc, DirSize); + false -> Acc + end, + %% Update reader if in sync + Rdr1 = case Id == LastReadId andalso Pos >= 0 of + true -> Rdr#reader{cache = {'Message', element(Iter, Msg)}, pos = Pos + Sign}; + false -> Rdr + end, + {Acc1, Rdr1}; + (_, A) -> A + end. + msg_stop_fun(MaxReadId, UID) -> msg_stop_fun(MaxReadId, UID, 1). msg_stop_fun(MaxReadId, UID, Dir) -> @@ -1870,10 +1931,10 @@ presence(#'Contact'{phone_id = PhoneId} = C) -> {ok, #'Profile'{presence = Presence, update = Update}} -> C#'Contact'{presence = Presence, update = Update}; {error, _} -> - info(?MODULE, "invalid contact: ~p", [PhoneId]), C end. + ?LOG_INFO("invalid contact: ~p", [PhoneId]), C end. get_data_val(Key, Data) -> - case get_data(Key, Data) of + case get_data(Key, Data) of [] -> []; #'Feature'{value = Val} -> Val @@ -1883,7 +1944,7 @@ get_data(Key, #'Desc'{data = Data}) when not is_record(Data, 'Desc') -> get_data(Key, Data) -> case lists:keyfind(Key, #'Feature'.key, Data) of false -> []; - F -> F + F -> F end. set_data(Key, Val, Data) -> case get_data(Key, Data) of @@ -1921,10 +1982,10 @@ get_feed_data(#p2p{from = PartyA, to = PartyB} = Feed, PhoneId) when PartyA == P #writer{cache = #'Message'{id = LastId}} = kvs_stream:load_writer(roster:feed_key(Feed)), {PhoneId, ReaderId, LastId, get_contact(Friend, PhoneId)}; {error, Reason} -> - info(?MODULE, "History/get.ContactError:~p", [Reason]), + ?LOG_INFO("History/get.ContactError:~p", [Reason]), {error, invalid_data, [], []} end; get_feed_data(Feed, PhoneId) -> - info(?MODULE, "~p:History/get.FeedError:~p", [PhoneId, Feed]), + ?LOG_INFO("~p:History/get.FeedError:~p", [PhoneId, Feed]), {error, invalid_feed, [], []}. get_feed_msg(Feed) -> get_feed_msg(Feed, 0, []). @@ -1945,7 +2006,7 @@ get_reader(Feed, RosterId) -> #p2p{from = From, to = To} -> [Opp]=[P||P<-[From, To], roster_id(P) /= RosterId], #'Contact'{reader = Reader} = roster:get_contact(Opp, phone_id(RosterId)), Reader; - _ -> info(?MODULE, "FeedErrorOccured:~p", [Feed]), 0 + _ -> ?LOG_INFO("FeedErrorOccured:~p", [Feed]), 0 end. %% utils @@ -1963,9 +2024,34 @@ next_key(Table, CurrentId, Count) -> NextId -> next_key(Table, NextId, Count-1) end. +%% Traverse a stream in direction Dir, starting from Id. Stops when Fun returns +%% {stop, Res} or when the stream runs out (returning {eos, Acc}). For the +%% direction, 'fwd' means increasing ids and 'bwd' is decreasing ids. +-spec fold_stream(Table :: atom(), + Fun :: fun((Element, Acc) -> {stop, Res} | {cont, Acc}), + Id :: ElementId, + Acc :: Acc, + Dir :: fwd | bwd) -> {stop, Res} | {eos, Acc} when + ElementId :: non_neg_integer(), + Element :: tuple(). +fold_stream(_Table, _Fun, [], Acc, _Dir) -> + %% Stream ran out + {eos, Acc}; +fold_stream(Table, Fun, Id, Acc, Dir) -> + {ok, Elem} = kvs:get(Table, Id), + case Fun(Elem, Acc) of + {stop, Res} -> {ok, Res}; + {cont, Acc1} -> + Next = case Dir of + fwd -> element(#iterator.prev, Elem); + bwd -> element(#iterator.next, Elem) + end, + fold_stream(Table, Fun, Next, Acc1, Dir) + end. + %% kvs utils %% TODO add to kvs fold(_, Acc, _, [], _, _) -> Acc; -fold(_, Acc, _, _, [], _) -> info(?MODULE, "uncertain direction", []), Acc; +fold(_, Acc, _, _, [], _) -> ?LOG_INFO("uncertain direction", []), Acc; fold(_, Acc, _, S, S, _) -> Acc; fold(Fun, Acc, Table, Start, Finish, Driver) -> Direction = case Start > Finish of true -> #iterator.next; false -> #iterator.prev end, @@ -1994,7 +2080,7 @@ fold(Fun, Acc, Table, Start, Finish, Driver, Direction, Count, GetFun) when Star {_, _} when Dir -> Acc2; _ -> fold(Fun, Acc2, Table, Prev, Finish, Driver, Direction, Count2) end; _ -> Acc end catch Err:Rea -> - n2o:error(?MODULE, "Catch fold2:~p~n", [n2o:stack_trace(Err, Rea)]), Acc end. + ?LOG_ERROR("Catch fold2:~p~n", [n2o:stack_trace(Err, Rea)]), Acc end. %%count(#reader{pos = P}, #writer{count = T}) -> T - P. @@ -2098,10 +2184,7 @@ while(F, R = #reader{pos = Pos}, UID, Ts) when Pos > 0 -> false -> while(F, NR#reader{args = [], dir = 1}, UID, Ts); El -> El end. %% Datetime utils -now_msec() -> now_msec(erlang:timestamp()). -now_msec({Mega, Sec, Micro}) -> (Mega * 1000000 + Sec) * 1000 + round(Micro / 1000). -msec_now(A) -> A0 = A / 1000, S = trunc(A0), Mega = S div 1000000, - Sec = S - Mega * 1000000, Micro = round((A0 - S) * 1000000), {Mega, Sec, Micro}. +now_msec() -> os:system_time(millisecond). msToUT(Milliseconds) -> BaseDate = calendar:datetime_to_gregorian_seconds({{1970, 1, 1}, {0, 0, 0}}), @@ -2153,13 +2236,13 @@ start_vnodes() -> [n2o_pi:start(#pi{module=n2o_vnode,table=ring,sup=n2o,state=[] hard_restart_vnodes() -> [try n2o_pi:restart(ring, Pos) catch Err:Rea -> - n2o:error(?MODULE,"Catch stop vnode:~p~n",[n2o:stack_trace(Err, Rea)]), + ?LOG_ERROR("Catch stop vnode:~p~n",[n2o:stack_trace(Err, Rea)]), case n2o_pi:pid(ring, Pos) of Pid when is_pid(Pid) -> exit(Pid, kill), - roster:info(?MODULE, "~p virtual node has being killed (~p)", [Pos, Pid]), + ?LOG_INFO("~p virtual node has being killed (~p)", [Pos, Pid]), n2o_pi:start(#pi{module=n2o_vnode,table=ring,sup=n2o,state=[],name=Pos}); - Data -> roster:error(?MODULE, "invalid pid data : ~p", [Data]), + Data -> ?LOG_ERROR("invalid pid data : ~p", [Data]), {error,{not_pid,Data}} end end || {_, Pos} <- lists:zip(n2o:ring(), lists:seq(1, length(n2o:ring())))]. @@ -2270,8 +2353,8 @@ init_default_fake_numbers() -> {error, _} -> kvs:put(#'FakeNumbers'{phone = PhoneNumber, created = now_msec()}); _ -> ok end || PhoneNumber <- ?FAKE_NUMBERS], - info(?MODULE, "InitiatedDefaultFakeNumbers", []); - _ -> info(?MODULE, "FakeNumberTableExists", []) + ?LOG_INFO("InitiatedDefaultFakeNumbers", []); + _ -> ?LOG_INFO("FakeNumberTableExists", []) end. add_fake_number(PhoneNumber) when is_binary(PhoneNumber) -> @@ -2282,8 +2365,8 @@ add_fake_number(PhoneNumber) when is_binary(PhoneNumber) -> %% TEST API check(Module) -> case lists:all(fun(X) -> X == ok end, Module:suite()) of - true -> roster:info("ALL TESTS PASSED", []), true; - false -> roster:info("TEST ERRORS", []), false end. + true -> ?LOG_INFO("ALL TESTS PASSED", []), true; + false -> ?LOG_INFO("TEST ERRORS", []), false end. n2o_pid(Name) -> diff --git a/apps/roster/src/roster_client.erl b/apps/roster/src/roster_client.erl index 3912fb78ce44dab8ec31f4c63b47855c29522d10..ae94ab202a839561a04e321a68fde7964b38906f 100644 --- a/apps/roster/src/roster_client.erl +++ b/apps/roster/src/roster_client.erl @@ -1,5 +1,6 @@ -module(roster_client). -compile(export_all). +-include_lib("kernel/include/logger.hrl"). -include_lib("n2o/include/n2o.hrl"). -include_lib("emqttc/include/emqttc_packet.hrl"). -include("roster.hrl"). @@ -52,14 +53,14 @@ filter(Term, _) -> case Term of {send_push, _, _, _} -> skip; #'Message'{type = [sys | _], status = [], files = Files} = _Msg -> - roster:info(?MODULE, "system:~p", [case Files of [] -> []; _ -> + ?LOG_INFO("system:~p", [case Files of [] -> []; _ -> (hd(Files))#'Desc'.payload end]); %% ignore muc system messages #'Contact'{presence = Presence} when Presence == online; Presence == offline -> skip; %% ignore Contact presence #'Contact'{status = internal} -> skip; %% ignore Contact presence #'Member'{presence = Presence, status = Status} when Status /= patch andalso (Presence == online orelse Presence == offline) -> skip; %% ignore Member presence - _ -> % roster:info("Term value: ~p", [Term]), + _ -> % ?LOG_INFO("Term value: ~p", [Term]), send end. filter_friend(Term, State) -> case {Term, filter(Term, State)} of @@ -74,12 +75,12 @@ proc(init, #handler{name = ?SYS_REST_CLIENT, state = #state{receive_pid = Self} {ok, Async#handler{state = State#state{mqttc = C, client_id = ?SYS_REST_CLIENT}, seq = 0}}; proc(init, #handler{name = ?SYS_BRIDGE_TEST_CLIENT, state = #state{receive_pid = Self} = State} = Async) -> {ok, C} = emqttc:start_link([{client_id, ?SYS_BRIDGE_TEST_CLIENT}, {logger, {console, error}}, {reconnect, 5}]), - roster:info(?MODULE, "MICRO BRIDGE SIMULATOR PROC started", []), + ?LOG_INFO("MICRO BRIDGE SIMULATOR PROC started", []), register(sys_bridge, C), Self ! init, {ok, Async#handler{state = State#state{mqttc = whereis(sys_bridge), client_id = ?SYS_BRIDGE_TEST_CLIENT}, seq = 0}}; proc(init, #handler{name = ClientId, state = #state{mqtt_opts = Opts, receive_pid = Self, username = Username} = State} = Async) -> - roster:info(?MODULE, "ClientInit:~p\r", [ClientId]), + ?LOG_INFO("ClientInit:~p\r", [ClientId]), {ok, C} = emqttc:start_link([{client_id, ClientId}, {clean_sess, false}, {logger, {console, error}}, {username, Username}, {reconnect, 5}] ++ Opts), @@ -103,7 +104,7 @@ proc({publish, _, BinTerm}, #handler{state = #state{receive_pid = Self, last_res Self ! Term; _ -> skip end, {reply, [], H#'handler'{state = State#state{last_res = [Term | LastRes]}}}; proc(_Term, #handler{state = #state{mqttc = {error, _} = Err}} = H) -> - roster:info("mqttc error: ~p", [Err]), + ?LOG_INFO("mqttc error: ~p", [Err]), {reply, [], H}; proc({callback, CallbackFun}, #handler{state = State} = H) -> State2 = CallbackFun(State), @@ -112,11 +113,21 @@ proc(Term, #handler{state = #state{mqttc = C}} = H) -> roster:send_event(C, <<>>, <<>>, Term), {reply, [], H}. +-ifdef(REMOTE_NODE). + stop_vnodes() when ?HOST == ?LOC -> roster:stop_vnodes(), timer:sleep(1000); stop_vnodes() -> skip. start_vnodes() when ?HOST == ?LOC -> roster:start_vnodes(), timer:sleep(1000); start_vnodes() -> skip. +-else. + +stop_vnodes() -> skip. +start_vnodes() -> skip. + +-endif. + + gen_name(Name) -> gen_name(Name, iolist_to_binary(["DevKey_", Name])). gen_name(Name, DevKey) -> gen_name(Name, DevKey, "emqttd_"). gen_name_reg(Name) -> gen_name_reg(Name, iolist_to_binary(["DevKey_", Name])). @@ -165,7 +176,7 @@ test_info(#'History'{roster_id = Phone, feed = Feed, data = Data} = History) -> data = [Msg#'Message'{feed_id = feed(MsgFeed), from = roster:phone_id(From), to = roster:phone_id(To)} || #'Message'{feed_id = MsgFeed, from = From, to = To} = Msg <- Data]}, Phone); -test_info(#'Message'{from= From, to=To, feed_id = Feed, files = Data} = Message) -> +test_info(#'Message'{from= From, to=_To, feed_id =_Feed, files =_Data} = Message) -> test_info(roster_message, Message#'Message'{}, From). @@ -283,7 +294,7 @@ stop_connect(Host, CurrentId, Count, MaxCount) -> NextId when Count =< MaxCount -> {ok, #'Auth'{client_id = ClientId}} = kvs:get('Auth', NextId), stop_client(ClientId), - roster:info(?MODULE, "stop connect. Number of connects is ~p", [MaxCount -Count-1]), + ?LOG_INFO("stop connect. Number of connects is ~p", [MaxCount -Count-1]), stop_connect(Host, NextId, Count+1, MaxCount); NextId -> {NextId, Count} end. diff --git a/apps/roster/src/roster_data.erl b/apps/roster/src/roster_data.erl index a03632729ca5b907b314c53dee53755ebf0ee655..834f914870c225e0663aaf05d2d439d038b1b9be 100644 --- a/apps/roster/src/roster_data.erl +++ b/apps/roster/src/roster_data.erl @@ -1,6 +1,7 @@ -module(roster_data). -compile(export_all). +-include_lib("kernel/include/logger.hrl"). -include("roster.hrl"). -include_lib("n2o/include/n2o.hrl"). @@ -131,7 +132,7 @@ friend_test_rand(N) when is_integer(N) -> friend_test_rand(RosterIds) -> try Rosters = [case R of #'Roster'{} -> R; _-> element(2, kvs:get('Roster', R)) end||R<-RosterIds], - Counter = length(Rosters), + _ = length(Rosters), %% Data = [{APhoneId, AClientId, AToken}, {BPhoneId, BClientId, BToken}] = start_rosters(Rosters), [APhoneId, BPhoneId] = [roster:phone_id(P, RosterId)||#'Roster'{phone = P, id = RosterId}<-Rosters], roster_client:test_info(roster_friend, #'Friend'{phone_id = APhoneId, friend_id = BPhoneId, status = request}, roster:phone(APhoneId)), @@ -143,7 +144,7 @@ friend_test_rand(RosterIds) -> %% roster_client:set_filer([AClientId, BClientId], filter), %% lists:map(fun roster_client:stop_client/1, ClientIds), ok catch Err:Rea -> - n2o:error(?MODULE, "Catch:~p~n", [n2o:stack_trace(Err, Rea)]), ok + ?LOG_ERROR("Catch:~p~n", [n2o:stack_trace(Err, Rea)]), ok end. get_roster_ids() -> @@ -190,7 +191,7 @@ create_room(RosterIds, Room, PageMembCount, Type) -> [roster_client:test_info(roster_room, #'Room'{status = add, id = Room, members = Mmbrs}, AdmPhone)|| Mmbrs <-Mmbrs2, Mmbrs /= []] %% roster_client:stop_client(AClientId) catch Err:Rea -> - n2o:error(?MODULE, "Catch:~p~n", [n2o:stack_trace(Err, Rea)]) + ?LOG_ERROR("Catch:~p~n", [n2o:stack_trace(Err, Rea)]) end, Room. create_channel() -> @@ -290,7 +291,7 @@ get_user_favorites(RosterId) -> case kvs:get('Roster', RosterId) of {ok, #'Roster'{favorite = FavList}} -> {length(FavList), FavList}; _ -> - roster:info(?MODULE, "RosterNotFound:~p", [RosterId]), + ?LOG_INFO("RosterNotFound:~p", [RosterId]), {0, []} end. diff --git a/apps/roster/src/roster_db.erl b/apps/roster/src/roster_db.erl index 50c2f46697875e34ba7a3a354ad81a0a5606baaa..a95ea7bec62fef43dc4347c012e3cdd3519524a0 100644 --- a/apps/roster/src/roster_db.erl +++ b/apps/roster/src/roster_db.erl @@ -1,6 +1,7 @@ -module(roster_db). -compile(export_all). +-include_lib("kernel/include/logger.hrl"). -include("roster.hrl"). -include("micro.hrl"). -include_lib("emqttd/include/emqttd.hrl"). @@ -36,7 +37,7 @@ fix_duration() -> #'Feature'{key = <<"DURATION">>, value = V}=F<-Data, is_integer(V)]. fix_duration2() -> - [M#'Message'{files = lists:keystore(Id, #'Desc'.id, Descs, + _ = [M#'Message'{files = lists:keystore(Id, #'Desc'.id, Descs, D#'Desc'{data = lists:keystore(FId, #'Feature'.id, Data, F#'Feature'{key = <<"DURATION">>})})} ||#'Message'{files = Descs}=M<-kvs:all('Message'), #'Desc'{id = Id, mime = <<"audio">>, data = Data} = D<-Descs, #'Feature'{key = <<"SIZE">>, id = FId}=F<-Data], @@ -53,7 +54,7 @@ fix_empty_desc_ids() -> find_invalid_rooms(Id) when is_integer(Id) -> find_invalid_rooms(Id, []). find_invalid_rooms(#'Roster'{roomlist = Rooms} = Roster, N) -> lists:flatten([case catch roster:room(Roster, R2) of - {'EXIT', _} -> false = R2; + {'EXIT', _} -> []; Room -> Room end || R2 = #'Room'{} <- case N of [] -> Rooms;_ -> roster:last_rooms(Rooms, 100) end]); @@ -73,7 +74,7 @@ check_desc_migration() -> delete_posttranslate_msg() -> [kvs:put(Msg#'Message'{files = lists:keydelete(Id, #'Desc'.id, Descs)}) ||#'Message'{files = Descs} = Msg<-kvs:all('Message'), - #'Desc'{mime = <<"posttranslate">>, id = Id} = D<- Descs]. + #'Desc'{mime = <<"posttranslate">>, id = Id} <- Descs]. del_rooms_with_one_msg() -> [[case kvs:get('Roster', roster:roster_id(PhoneId)) of @@ -82,7 +83,7 @@ del_rooms_with_one_msg() -> #'Message'{next = [], prev = []} -> roster:purge_room(To); _ -> [] end; _ -> [] end || PhoneId <- case Feed of #muc{} -> [From]; _ -> [From, To] end] - || Msg = #'Message'{id = Id, feed_id = Feed, from = From, to = To}<-kvs:all('Message')]. + || Msg = #'Message'{feed_id = Feed, from = From, to = To}<-kvs:all('Message')]. clean_msg_without_user() -> L = lists:flatten([ @@ -94,12 +95,12 @@ clean_msg_without_user() -> end; _ -> [] end - catch Err:Rea -> [] + catch _Err:_Rea -> [] end || PhoneId <- case Feed of #muc{} -> [From]; _ -> [From, To] end] - || Msg = #'Message'{id = Id, feed_id = Feed, from = From, to = To}<-kvs:all('Message')]), + || Msg = #'Message'{feed_id = Feed, from = From, to = To}<-kvs:all('Message')]), lists:ukeysort(#'Message'.created, sets:to_list(sets:from_list(L))). get_room_message(Muc) -> @@ -109,7 +110,7 @@ get_p2p_message(P2P) -> [Msg || Msg = #'Message'{feed_id = P2P2} <- kvs:all('Message'), P2P == P2P2]. get_entity(Feed, Fun) -> - #writer{cache=Last,first=First}= kvs_stream:load_writer(Feed), + #writer{cache=Last}= kvs_stream:load_writer(Feed), roster:entries(Fun, element(2,Last), element(1,Last), undefined). %fun(A, Acc) -> case element(2, A) > Limit of true -> Acc; false -> [A | Acc] end end) @@ -125,9 +126,9 @@ fix_empty_services() -> test_db_accounts() -> lists:flatten([case catch roster:roster(Id) of - {error, {user, {Local, PhoneId}} = Res} -> Res; - {error, {room, {PhoneId, Room}, Res} = R} -> R; - R -> [] + {error, {user, {_Local, _PhoneId}} = Res} -> Res; + {error, {room, {_PhoneId, _Room},_Res} = R} -> R; + _R -> [] end || #'Roster'{id = Id} <- kvs:all('Roster')]). purge_invalid_roooms() -> purge_invalid_roooms(10, []). @@ -161,9 +162,9 @@ fix_rosters_names() -> case kvs:get('Roster', RId = roster:roster_id(PhoneId)) of {ok, #'Roster'{names = Name, userlist = [#'Contact'{} | _]} = R} -> case {Name, RId} of - {<>, _} -> + {<<_, _/binary>>, _} -> {[Cont#'Contact'{names = Name} | Acc], R}; - {_, Id} -> NN = case CName of <> -> CName; _ -> + {_, Id} -> NN = case CName of <<_, _/binary>> -> CName; _ -> list_to_binary(random_string(lists:seq(1, 10))) end, {[Cont#'Contact'{names = NN} | Acc], R#'Roster'{names = NN}}; _ when is_binary(CName) -> @@ -174,7 +175,7 @@ fix_rosters_names() -> {[Cont#'Contact'{names = NN} | Acc], Rost} end; E -> %roster:purge_user(Phone), - roster:info(?MODULE, "Shit:~p", [E]), + ?LOG_INFO("Shit:~p", [E]), {Acc, Rost} end; (_, Res) -> Res end, {[], Roster}, L), @@ -329,16 +330,16 @@ search_room(Keyword) -> end, FilterFun = - fun(Table, NextId, Keyword) -> + fun(Table, NextId, KW) -> {ok, #'Room'{name = Name} = Next} = kvs:get(Table, NextId), - [KeyWLow, LowName] = [string:lowercase(Word) || Word <- [Keyword, Name]], + [KeyWLow, LowName] = [string:lowercase(Word) || Word <- [KW, Name]], case binary:match(LowName, [KeyWLow]) of nomatch -> []; _ -> [Next] end end, Res = search_data(Keyword, {mnesia:dirty_first('Room'), []}, 'Room', NextFun, FilterFun), T1 = erlang:timestamp(), - roster:info(?MODULE, "TIME for room search in ms:~p", [timer:now_diff(T1, T0) / 1000]), + ?LOG_INFO("TIME for room search in ms:~p", [timer:now_diff(T1, T0) / 1000]), Res. get_features() -> get_features([<<"image">>, <<"video">>, <<"thumb">>]). @@ -413,7 +414,7 @@ get_ips(Begin) -> Begin == hd(binary:split(IP, <<".">>))])). validate() -> - lists:flatten([validate_table(Table) || #table{name = Table}<-roster:tables(), Rec<-kvs:all(Table), + lists:flatten([validate_table(Table) || #table{name = Table}<-roster:tables(), not lists:member(Table, [chain, writer, reader])]). validate_table(Table) -> @@ -452,18 +453,14 @@ fix_jobs() -> [opt_put(J#'Job'{settings = case S of #'Feature'{} -> [S]; _-> S end}, J) || J = #'Job'{settings = S}<-kvs:all('Job')], ok. -find_msg_by_text(Text) when is_binary(Text) -> - [M||M=#'Message'{files = Descs}<-kvs:all('Message'), #'Desc'{mime = <<"text">>, payload = <>} <- Descs]; -find_msg_by_text(_) -> []. - broken_chains_feed(Tab) -> lists:flatten([ begin Feed=element(#iterator.feed_id,M), Id=element(#iterator.id,M), case kvs_stream:load_writer(Feed) of - #writer{cache = CE, first = FE} when (element(2,CE)==Id orelse element(2,FE)==Id) -> []; - #writer{cache = CE, first = FE} ->Feed; - %fix_chain(W), - _ -> kvs:remove(element(1,M),Id), Feed end end - || M <- kvs:all(Tab),(element(#iterator.prev,M)/= [] andalso element(#iterator.id,M) > element(#iterator.prev,M)) + #writer{cache = CE, first = FE} when (element(2,CE)==Id orelse element(2,FE)==Id) -> []; + #writer{cache =_CE, first =_FE} -> Feed; + %fix_chain(W), + _ -> kvs:remove(element(1,M),Id), Feed end end + || M <- kvs:all(Tab),(element(#iterator.prev,M)/= [] andalso element(#iterator.id,M) > element(#iterator.prev,M)) orelse (element(#iterator.next,M)/= [] andalso element(#iterator.id,M) < element(#iterator.next,M)) orelse element(#iterator.next,M)== [] orelse element(#iterator.prev,M)==[] ]) % del_invalid_messages() @@ -480,7 +477,7 @@ broken_chains_feed(Tab) -> set_prev(E,Start,N)-> Fun = fun(A, Acc) -> [A | Acc] end, - % roster:info(?MODULE, "Start:~p", [Start]), + % ?LOG_INFO("Start:~p", [Start]), case kvs:fold(Fun, [Start], element(1,E), element(2,Start), N, #iterator.next, #kvs{mod = store_mnesia}) of [H] -> H; [H1,H2|_] -> @@ -495,7 +492,7 @@ set_prev(E,Start,N)-> valid_chains({Tab,feed},Id,Acc) -> [Entity]=mnesia:dirty_read(Tab,Id), -% roster:info(?MODULE, "Start:~p", [Acc]), +% ?LOG_INFO("Start:~p", [Acc]), Feed=element(#iterator.feed_id, Entity), case lists:keyfind(Feed, 1, Acc) of {Feed,Ids} -> lists:keyreplace(Feed, 1, Acc, {Feed,[Id]++Ids}); @@ -507,7 +504,7 @@ valid_chains({Tab,feed},Id,Acc) -> valid_chains({Tab,Feed},Id,Acc) -> [Entity]=mnesia:dirty_read(Tab,Id), -% roster:info(?MODULE, "Start:~p", [Acc]), +% ?LOG_INFO("Start:~p", [Acc]), case element(#iterator.feed_id, Entity) of Feed ->[Id]++Acc; _ -> Acc @@ -516,38 +513,42 @@ valid_chains({Tab,Feed},Id,Acc) -> repair_chain(_,[],_) -> []; -repair_chain(Tab,[Id],N) -> [One]=mnesia:dirty_read(Tab,Id), - case kvs_stream:load_writer(element(#iterator.feed_id,One)) of - #writer{count=C}=W when C==1 -> One1=setelement(#iterator.next,One, []), - One2=setelement(#iterator.prev,One1, []), - kvs:put(W#writer{cache=One2, first=One2}),[One2]; - #writer{count=C}=W -> One1=setelement(#iterator.prev,One,[]), - kvs:put(W#writer{count=N, cache=One1}),[One1]; - _ -> kvs:remove(element(1,One),element(2,One)), [] - end; -repair_chain(Tab,[H|T]=NewL,N) -> +repair_chain(Tab,[Id],N) -> + [One]=mnesia:dirty_read(Tab,Id), + case kvs_stream:load_writer(element(#iterator.feed_id,One)) of + #writer{count=C}=W when C==1 -> + One1=setelement(#iterator.next,One, []), + One2=setelement(#iterator.prev,One1, []), + kvs:put(W#writer{cache=One2, first=One2}),[One2]; + #writer{}=W -> + One1=setelement(#iterator.prev,One,[]), + kvs:put(W#writer{count=N, cache=One1}),[One1]; + _ -> + kvs:remove(element(1,One),element(2,One)), [] + end; +repair_chain(Tab,[_H|T]=NewL,N) -> %NewL=[H|T]=lists:usort(List), - % roster:info(?MODULE, "Start:~p", [NewL]), + % ?LOG_INFO("Start:~p", [NewL]), Fun=fun(A,{AOut, []})-> {AOut++repair_chain(Tab,[A],N),[]}; (A,{AOut, [Ap|TIn]})-> - [E]=mnesia:dirty_read(Tab,A), - {E1,NEl}=case AOut of - [] -> - NA=setelement(#iterator.next,E, []), - kvs_stream:save((kvs_stream:load_writer(element(#iterator.feed_id,E)))#writer{first=NA}), - {NA, []}; - _ -> %[Elast]=mnesia:dirty_read(Tab,), - Next=lists:last(AOut), - {setelement(#iterator.next,E,Next), Next} - end, - %[Ep]=mnesia:dirty_read(Tab,Ap), - case (element(#iterator.prev,E)==Ap andalso element(#iterator.next,E)==NEl) of - true -> skip; - _ -> kvs:put(setelement(#iterator.prev,E1,Ap)) - end, - {AOut++[A],TIn} - end, - lists:foldl(Fun, {[],T}, NewL),ok. + [E]=mnesia:dirty_read(Tab,A), + {E1,NEl}=case AOut of + [] -> + NA=setelement(#iterator.next,E, []), + kvs_stream:save((kvs_stream:load_writer(element(#iterator.feed_id,E)))#writer{first=NA}), + {NA, []}; + _ -> %[Elast]=mnesia:dirty_read(Tab,), + Next=lists:last(AOut), + {setelement(#iterator.next,E,Next), Next} + end, + %[Ep]=mnesia:dirty_read(Tab,Ap), + case (element(#iterator.prev,E)==Ap andalso element(#iterator.next,E)==NEl) of + true -> skip; + _ -> kvs:put(setelement(#iterator.prev,E1,Ap)) + end, + {AOut++[A],TIn} + end, + lists:foldl(Fun, {[],T}, NewL),ok. dirty_foldl(Fun, Acc, E) -> dirty_foldl(Fun, Acc, element(1, E), E). dirty_foldl(Fun, Acc, Tab, E) -> @@ -598,14 +599,14 @@ backup(Schema, Tables, Node, File, Opts) when is_list(Tables) -> ok -> mnesia:deactivate_checkpoint(Name), ok; {error, Reason} = Err -> - roster:info(?MODULE, "~p", [Reason]), + ?LOG_INFO("~p", [Reason]), mnesia:deactivate_checkpoint(Name), Err end; {error, {"Cannot prepare checkpoint (replica not available)", [Table, _]}} -> backup(Schema, Tables -- [Table], Node, File, Opts); {error, Reason} = Err -> - roster:info(?MODULE, "~p", [Reason]), + ?LOG_INFO("~p", [Reason]), Err end. @@ -614,7 +615,7 @@ restore(File, Node, SkipTbs) -> application:stop(bpe), case mnesia:restore(File, [{skip_tables, SkipTbs}]) of {aborted, {no_exists, Tab}} -> restore(File, Node, [Tab | SkipTbs]); - {aborted, Reason} -> roster:info(?MODULE, "aborted backup restore from ~s by reason ~p", [File, Reason]), + {aborted, Reason} -> ?LOG_INFO("aborted backup restore from ~s by reason ~p", [File, Reason]), {error, Reason}; {atomic, Tabs} -> application:start(bpe), roster:restart_module(roster_bpe), @@ -740,13 +741,13 @@ broken_feeds(Opts) -> Map = lists:foldl(CollectMessagesByFeed, maps:new(), lists:sort(mnesia:dirty_all_keys('Message'))), Fun = fun(_Key, #{msg:= #'Message'{id = MsgId, feed_id = F}, status := error}, Acc) -> case lists:member("debug", Opts) of - true -> roster:error(?MODULE, "Feed: ~p~nMsgId: ~p~n", [F, MsgId]); + true -> ?LOG_ERROR("Feed: ~p~nMsgId: ~p~n", [F, MsgId]); false -> ok end, [#{feed => F, msg_id => MsgId} | Acc]; (_Key, #{msg:= #'Message'{feed_id = F}, status := ok}, Acc) -> case lists:member("debug", Opts) of - true -> roster:info(?MODULE, "Ok feed: ~p~n", [F]); + true -> ?LOG_INFO("Ok feed: ~p~n", [F]); false -> ok end, Acc @@ -833,7 +834,7 @@ fixed_messages (Opts)-> % find messages that actually needs update NeedsUpdateFn = fun(Msg) -> case kvs:get('Message',element(2,Msg)) of {ok,Msg} -> false; - {ok,Old} -> {true,Msg}; + {ok,_} -> {true,Msg}; Other -> erlang:error( Other) end end, lists:filtermap(NeedsUpdateFn,ReorderedMessages). @@ -850,7 +851,7 @@ fix_broken_feeds (Opts) -> loop_rosters() -> FirstId = mnesia:dirty_first('Roster'), loop_rosters(kvs:get('Roster', FirstId), [], []). -loop_rosters({error, not_found}, Acc, Ers) -> +loop_rosters({error, not_found},_Acc, Ers) -> %{lists:flatten(Acc), lists:filter(fun({_, Err}) -> Err =/= [] end, Ers)}; lists:flatten(Ers); loop_rosters({ok, #'Roster'{id = Id, userlist = Contacts} = R}, Acc, Ers) -> @@ -937,9 +938,9 @@ correct_numbers([Number | Rest], Acc) -> case lists:filter(fun(Code) -> case string:split(Number, Code) of [Number] -> false; - [<<>>, <<"0", GoodNumber/binary>>] -> true; - [<<>>, GoodNumber] -> true; - _ -> false + [<<>>, <<"0",_GoodNumber/binary>>] -> true; + [<<>>,_GoodNumber] -> true; + _ -> false end end, country_codes()) of [CountryCode] -> @@ -997,35 +998,12 @@ all_contacts([Rid | Rest], Acc) -> Filtered = lists:filter(fun(#'Contact'{phone_id = Pid}) -> Pid =/= roster:phone_id(Rid) end, Contacts), all_contacts(Rest, [Filtered | Acc]). -add_linkage([], Acc1, Acc2) -> - io:format("Acc1: ~p~nAcc2: ~p~n", [Acc1, Acc2]); -add_linkage([{PhoneMiss, PhoneHas} | Rest], Acc1, Acc2) -> - case kvs:index('LinkProfile', phone, PhoneHas) of - [#'LinkProfile'{id = P_UUID}] -> - LinkP = #'LinkProfile'{id = P_UUID, phone = PhoneMiss}, - {ok, #'Profile'{rosters = [RidMiss]}} = kvs:get('Profile', PhoneMiss), - {ok, #'Profile'{rosters = [RidHas]}} = kvs:get('Profile', PhoneHas), - case kvs:index('LinkRoster', phone_id, roster:phone_id(RidHas)) of - [#'LinkRoster'{id = R_UUID}] -> - LinkR = #'LinkRoster'{id = R_UUID, phone_id = roster:phone_id(RidMiss)}, - {struct, Json1} = json_rec:to_json(LinkP, roster_export), - {struct, Json2} = json_rec:to_json(LinkR, roster_export), - add_linkage(Rest, - [jsx:encode(Json1) | Acc1], - [jsx:encode(Json2) | Acc2]); - [] -> - io:format("Error LinkRoster ~p~n", [PhoneHas]) - end; - [] -> - io:format("ERROR Link Proile: ~p~n Acc: ~p~n", [PhoneHas, PhoneMiss]) - end. - read_link(FileName) -> case file:read_file(FileName) of {ok, Text} -> read_link(string:split(Text, ";", all), []); {error, _} = Reason -> - roster:info(?MODULE, "read_link file not read ~p", [Reason]), + ?LOG_INFO("read_link file not read ~p", [Reason]), [] end. @@ -1045,7 +1023,7 @@ loop_rooms() -> loop_rooms([], Acc, Ers) -> {lists:usort(lists:flatten(Acc)), lists:flatten(Ers)}; loop_rooms([RoomId | Rest], Acc, Ers) -> - {ok, R = #'Room'{admins = Admins, members = Members}} = kvs:get('Room', RoomId), + {ok, #'Room'{admins = Admins, members = Members}} = kvs:get('Room', RoomId), {NewAdmins, AErr} = loop_members(Admins, [], []), {NewMembers, MErr} = loop_members(Members, [], []), % Uncomment to save to mnesia diff --git a/apps/roster/src/roster_export.erl b/apps/roster/src/roster_export.erl deleted file mode 100644 index 0d2b71a8268f0f76ae33c1f7c1aab193618a909d..0000000000000000000000000000000000000000 --- a/apps/roster/src/roster_export.erl +++ /dev/null @@ -1,70 +0,0 @@ --module(roster_export). --include("roster.hrl"). --include("micro.hrl"). - --compile(export_all). --compile({parse_transform, exprecs}). --export_records(['Profile', 'Roster', 'Service', 'Feature', - 'Contact', 'Room', 'ExtendedStar', 'Tag', - 'Desc', 'Star', 'Message', p2p, muc, 'Member', 'LinkRoster', 'LinkProfile']). - -to_file(Table, File) -> - file:write_file(File, to_json(Table)). - -json(Rec, Acc) -> Acc++[to_json(Rec)]. - -to_json(Table, Fun, Acc) when is_atom(Table) -> - iolist_to_binary(mochijson2:encode(roster_db:fold(Fun, Acc, Table))). -to_json(Table, Id) -> - case kvs:get(Table, Id) of {ok, Rec} -> to_json(Rec); Err -> Err end. -to_json(#'Roster'{surnames = [_|_] = Surname} = R) -> - to_json(R#'Roster'{surnames = iolist_to_binary(Surname)}); -to_json(#'Roster'{names = [_|_] = Name} = R) -> - to_json(R#'Roster'{names = iolist_to_binary(Name)}); -to_json(#'Roster'{nick = [_|_] = Nick} = R) -> - to_json(R#'Roster'{nick = iolist_to_binary(Nick)}); -to_json(#'Roster'{favorite = [_|_]} = R) -> - to_json(R#'Roster'{favorite = []}); -to_json(#'Roster'{tags = T} = R) when is_list(T) -> - to_json(add_phone(R)); -to_json(#'Roster'{userlist = U} = R) when is_list(U) -> - to_json(add_profile_UUID(R)); -to_json(#'Roster'{roomlist = L} = R) when is_list(L) -> - to_json(add_roster_UUID(R)); -to_json(#'LinkProfile'{id = <<_:128>> = Id} = LP) -> - to_json(LP#'LinkProfile'{id = micro:norm_uuid(Id)}); -to_json(#'LinkRoster'{id = <<_:128>> = Id} = LP) -> - to_json(LP#'LinkRoster'{id = micro:norm_uuid(Id)}); -to_json(Table) when is_atom(Table) -> - to_json(Table, fun json/2, []); -to_json(Rec) when is_tuple(Rec), is_atom(element(1, Rec)) -> - json_rec:to_json(Rec, ?MODULE). - -add_phone(#'Roster'{phone = P} = R) -> - case kvs:get('Profile', P) of - {ok, #'Profile'{settings = S}} -> - Phone = - case roster:get_data_val(<<"PHONE">>, S) of - [] -> - case catch uuid:is_valid(binary_to_list(P)) of - true -> <<"">>; - {'EXIT', _} -> P - end; - Data -> Data - end, - R#'Roster'{tags = Phone}; - {error, not_found} -> R#'Roster'{tags = <<"">>} - end. - -add_profile_UUID(#'Roster'{phone = P} = R) -> - case kvs:index('LinkProfile', phone, P) of - [#'LinkProfile'{id = Id} | _] -> R#'Roster'{userlist = Id}; - [] -> R#'Roster'{userlist = <<"">>} - end. - -add_roster_UUID(#'Roster'{phone = P} = R) -> - case kvs:index('LinkRoster', phone_id, roster:phone_id(P)) of - [#'LinkRoster'{id = Id} | _] -> R#'Roster'{roomlist = Id}; - [] -> R#'Roster'{roomlist = <<"">>} - end. - diff --git a/apps/roster/src/roster_io.erl b/apps/roster/src/roster_io.erl deleted file mode 100644 index c06cb8b5ac360267c3a287068e78e7a20379a3ba..0000000000000000000000000000000000000000 --- a/apps/roster/src/roster_io.erl +++ /dev/null @@ -1,90 +0,0 @@ --module(roster_io). - --export([info/3, warning/3, error/3, log/4, log_modules/0, log_level/0, parse/1]). - --define(LOGGER, roster_io). - -%% -%% Main Logic -%% - -info(Module, String, Args) -> - io:format(format_message(Module, String), to_one_row(Args)). - -warning(Module, String, Args) -> - io:format(format_message(Module, String), Args). - -error(Module, String, Args) -> - io:format(format_message(error, Module, String), to_one_row(Args)). - -format_message(Module, String) -> - format_message([], Module, String). -format_message(error, Module, String) -> - format_message("\t***ERROR***\t", Module, String); -format_message(AttentionModificator, Module, String) -> - nitro:to_list([roster:timestamp_to_datetime(roster:now_msec()), "::", Module, ":", AttentionModificator, ":", String, "~n"]). - -log_modules() -> [?MODULE]. -log_level() -> info. - --define(LOG_MODULES, (application:get_env(roster,log_modules,roster))). --define(LOG_LEVEL, (application:get_env(roster,log_level,roster))). - -log_level(none) -> 3; -log_level(error) -> 2; -log_level(warning) -> 1; -log_level(_) -> 0. - -log(Module, String, Args, Fun) -> - case log_level(Fun) < log_level(?LOG_LEVEL:log_level()) of - true -> skip; - false -> case ?LOG_MODULES:log_modules() of - any -> ?LOGGER:Fun(Module, String, Args); - Allowed -> case lists:member(Module, Allowed) of - true -> ?LOGGER:Fun(Module, String, Args); - false -> skip end end end. -%% -%% Helpers -%% - -to_one_row(Args) -> - [case is_tuple(Arg) of - true -> tuple_to_string(Arg); - _ -> - case check_is_list_of_tuples(Arg) of - true -> re:replace([tuple_to_string(A) || A <- Arg], "\\n\\s+", "", [global,{return,list}]); - _ -> Arg end - end || Arg <- Args]. - -tuple_to_string(Arg) -> - re:replace(lists:flatten(io_lib:format("~p", [Arg])), "\\n\\s+", "", [global,{return,list}]). - -%% Parse new format of logged data -%% NOTE! Can be removed to other module - -parse(Data) -> -%% parse from string to tuple - case check_is_string(Data) of - false -> info(?MODULE, "Input data should be string", []); - _ -> - {ok, Ts, _} = erl_scan:string(dot_ended(Data)), - {ok, Tup} = erl_parse:parse_term(Ts), - Tup end. - -dot_ended(Data) -> -%% Check does input data contains dot at the end (required for erl_scan and erl_parse libs). If not - add it) - case lists:suffix(".", Data) of true -> Data; _ -> Data ++ "." end. - -check_is_string(Data) -> -%% check is input data a string (really string, not a list) - case is_list(Data) of false -> false; true -> case Data of [_] -> false; _ -> true end end. - -check_is_list_of_tuples(Data) -> -%% check is input data a list (really list, not a string) - case is_list(Data) of - false -> false; - true -> -%% check each element - is it tuple or not - ContainsOnlyTuples = sets:to_list(sets:from_list(lists:foldl(fun(El, Acc) -> case is_tuple(El) of true -> Acc ++ [true]; _ -> Acc ++ [false] end end, [], Data))), - case ContainsOnlyTuples of [true] -> true; _ -> false end - end. \ No newline at end of file diff --git a/apps/roster/src/roster_proto.erl b/apps/roster/src/roster_proto.erl index 1e2d3bdff188b6de998b6ad80d8b5da5b291e8ac..98f671450cc08f7cc8f6f36b82cda82a11be30da 100644 --- a/apps/roster/src/roster_proto.erl +++ b/apps/roster/src/roster_proto.erl @@ -1,4 +1,5 @@ -module(roster_proto). +-include_lib("kernel/include/logger.hrl"). -include("roster.hrl"). -include_lib("n2o/include/n2o.hrl"). -include_lib("kvs/include/metainfo.hrl"). @@ -41,5 +42,5 @@ do_info(#'Auth'{} = Auth, Req, #cx{params = <<"sys_" , do_info(#'Auth'{} = Auth, Req, State) -> roster_auth :info(Auth, Req, State); do_info(Message, Req, State) -> - roster:info(?MODULE, "UNKNOWN:~p, ~p", [Message, State]), + ?LOG_INFO("UNKNOWN:~p, ~p", [Message, State]), {unknown, Message, Req, State}. \ No newline at end of file diff --git a/apps/roster/src/roster_rest.erl b/apps/roster/src/roster_rest.erl index 9204c55ca094a81eb2b1c0aee5cc6d73ef4186e5..c8c838c7565109e0da12ace546aa71f1dccf4021 100644 --- a/apps/roster/src/roster_rest.erl +++ b/apps/roster/src/roster_rest.erl @@ -1,19 +1,20 @@ -module(roster_rest). +-include_lib("kernel/include/logger.hrl"). -compile(export_all). send_request(Method, RequestData, HTTPOptions, Options) -> - roster:info(?MODULE, "Request {~p, ~p}~n~n", [Method, RequestData]), + ?LOG_INFO("Request {~p, ~p}~n~n", [Method, RequestData]), case httpc:request(Method, RequestData, HTTPOptions, Options) of {ok, {{_, 200, _}, _, Body}} -> {ok, Body}; - {ok, {{_, HTTPStatusCode, HTTPStatus}, _, Body} = Result} -> + {ok, {{_, HTTPStatusCode, HTTPStatus}, _, Body}} -> Err = {[{http_code, HTTPStatusCode}, {http_status, HTTPStatus}, {body, Body}]}, - roster:error(?MODULE, "Response ~p~n~n", [Err]), + ?LOG_ERROR("Response ~p~n~n", [Err]), {error, Err}; {error, Result} -> - roster:error(?MODULE, "Response ~p~n~n", [Result]), + ?LOG_ERROR("Response ~p~n~n", [Result]), {error, Result} end. test() -> - send_request(get, {"https://api.voximplant.com/platform_api/BindUser?account_id=1152724&api_key=94b9f0f0-7d93-4e63-9061-aef62c30182b&user_id=82428&application_name=videoconf.nynja.voximplant.com", []}, [], []). \ No newline at end of file + send_request(get, {"https://api.voximplant.com/platform_api/BindUser?account_id=1152724&api_key=94b9f0f0-7d93-4e63-9061-aef62c30182b&user_id=82428&application_name=videoconf.nynja.voximplant.com", []}, [], []). diff --git a/apps/roster/src/roster_validator.erl b/apps/roster/src/roster_validator.erl index ea5d1c818f74323a1d884f78e1ed29693fb2d005..648ed7360da46a7efd9e5ca7e075b8268b80ce58 100644 --- a/apps/roster/src/roster_validator.erl +++ b/apps/roster/src/roster_validator.erl @@ -1,982 +1,1215 @@ -module(roster_validator). --include_lib("bpe/include/bpe.hrl"). --include_lib("emqttd/include/emqttd.hrl"). --include_lib("kvs/include/feed.hrl"). --include_lib("kvs/include/group.hrl"). --include_lib("kvs/include/kvs.hrl"). + +-include("roster.hrl"). +-include("static_auth.hrl"). +-include_lib("n2o/include/n2o.hrl"). -include_lib("kvs/include/metainfo.hrl"). -include_lib("kvs/include/user.hrl"). --include_lib("n2o/include/n2o.hrl"). --include_lib("roster/include/roster.hrl"). --include_lib("roster/include/static_auth.hrl"). --compile(export_all). +-include_lib("kvs/include/group.hrl"). +-include_lib("emqttd/include/emqttd.hrl"). +-include_lib("mnesia/src/mnesia.hrl"). -custom_validate(_Obj) -> []. -validate(Obj) -> validate(Obj, [], application:get_env(bert, custom_validate, {?MODULE, custom_validate})). -validate(Obj, Acc, _) when is_atom(Obj) -> Acc; -validate(Obj, Acc, _) when is_integer(Obj) -> Acc; -validate(Obj, Acc, _) when is_binary(Obj) -> Acc; +-compile(export_all). +-define(COND_FUN(Cond), fun(Rec) when Cond -> true; (_) -> false end). +validate(Obj) -> validate(Obj, []). +validate(_, [{[_|_] , _R}|_] = Acc) -> {error, Acc}; +validate([], _) -> ok; +validate(Objs, [{[] , R}|T]) -> validate(Objs, [R|T]); +validate([{CondFun, _, []}|T], Acc) when is_function(CondFun) -> validate(T, Acc); +validate([{CondFun, Field, [Obj|TObjs]}|T], Acc) when is_function(CondFun) -> + case CondFun(Obj) of + true -> validate([{CondFun, Field, TObjs}|T], Acc); + false -> {error, [Field, Obj|Acc]} end; +validate([{CondFun, Field, Obj}|T], Acc) when is_function(CondFun) -> + case CondFun(Obj) of true -> validate(T, Acc); false -> {error, [Field, Obj|Acc]} end; +validate([{_Field, []}|T], Acc) -> validate(T, Acc); +validate([{Field, [Obj|TObjs]}|T], Acc) -> + case validate(Obj, [Field|Acc]) of + ok -> validate([{Field, TObjs}|T], Acc); + Err -> Err + end; -validate(D = #'writer'{id = Id, count = Count, cache = Cache, args = Args, first = First}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {id,_} -> Acc2; - {count,_} when is_integer(Count) -> Acc2; - {cache,_} when Cache==[] orelse is_tuple(Cache) -> Acc2; - {args,_} -> Acc2; - {first,_} when First==[] orelse is_tuple(First) -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'writer'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'reader'{id = Id, pos = Pos, cache = Cache, args = Args, feed = Feed, dir = Dir}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {id,_} -> Acc2; - {pos,_} when Pos==[] orelse is_integer(Pos) -> Acc2; - {cache,_} when Cache==[] orelse is_integer(Cache) -> Acc2; - {args,_} -> Acc2; - {feed,_} -> Acc2; - {dir,_} when Dir==0 orelse Dir==1 -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'reader'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'cur'{id = Id, top = Top, bot = Bot, dir = Dir, reader = Reader, writer = Writer, args = Args}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {id,_} -> Acc2; - {top,_} when Top==[] orelse is_integer(Top) -> Acc2; - {bot,_} when Bot==[] orelse is_integer(Bot) -> Acc2; - {dir,_} when Dir==0 orelse Dir==1 -> Acc2; - {reader,_} when Reader==[] orelse is_tuple(Reader) -> Acc2; - {writer,_} when Writer==[] orelse is_tuple(Writer) -> Acc2; - {args,_} when is_list(Args) -> - lists:foldl(fun(Tmp, Acc3) when is_tuple(Tmp) orelse is_integer(Tmp) -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{args, D}|Acc3] end, Acc2, Args); - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'cur'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'iter'{id = Id, container = Container, feed = Feed, next = Next, prev = Prev}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {id,_} when Id==[] orelse is_integer(Id) -> Acc2; - {container,_} when is_atom(Container) -> Acc2; - {feed,_} -> Acc2; - {next,_} when Next==[] orelse is_integer(Next) -> Acc2; - {prev,_} when Prev==[] orelse is_integer(Prev) -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'iter'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'container'{id = Id, top = Top, rear = Rear, count = Count}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {id,_} when Id==[] orelse is_integer(Id) -> Acc2; - {top,_} when Top==[] orelse is_integer(Top) -> Acc2; - {rear,_} when Rear==[] orelse is_integer(Rear) -> Acc2; - {count,_} when is_integer(Count) -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'container'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'iterator'{id = Id, container = Container, feed_id = Feed_id, prev = Prev, next = Next, feeds = Feeds}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {id,_} when Id==[] orelse is_integer(Id) -> Acc2; - {container,_} when is_atom(Container) -> Acc2; - {feed_id,_} -> Acc2; - {prev,_} when Prev==[] orelse is_integer(Prev) -> Acc2; - {next,_} when Next==[] orelse is_integer(Next) -> Acc2; - {feeds,_} when is_list(Feeds) -> []; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'iterator'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'log'{id = Id, top = Top, rear = Rear, count = Count}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {id,_} when Id==[] orelse is_integer(Id) -> Acc2; - {top,_} when Top==[] orelse is_integer(Top) -> Acc2; - {rear,_} when Rear==[] orelse is_integer(Rear) -> Acc2; - {count,_} when is_integer(Count) -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'log'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'operation'{id = Id, container = Container, feed_id = Feed_id, prev = Prev, next = Next, feeds = Feeds}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {id,_} when Id==[] orelse is_integer(Id) -> Acc2; - {container,_} when is_atom(Container) -> Acc2; - {feed_id,_} -> Acc2; - {prev,_} when Prev==[] orelse is_integer(Prev) -> Acc2; - {next,_} when Next==[] orelse is_integer(Next) -> Acc2; - {feeds,_} when is_list(Feeds) -> []; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'operation'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'feed'{id = Id, top = Top, rear = Rear, count = Count}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {id,_} when Id==[] orelse is_integer(Id) -> Acc2; - {top,_} when Top==[] orelse is_integer(Top) -> Acc2; - {rear,_} when Rear==[] orelse is_integer(Rear) -> Acc2; - {count,_} when is_integer(Count) -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'feed'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'task'{name = Name, module = Module, prompt = Prompt, roles = Roles}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {name,_} when Name==[] orelse is_atom(Name) -> Acc2; - {module,_} when Module==[] orelse is_atom(Module) -> Acc2; - {prompt,_} when is_list(Prompt) -> - lists:foldl(fun(Tmp, Acc3) when is_tuple(Tmp) -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{prompt, D}|Acc3] end, Acc2, Prompt); - {roles,_} when is_binary(Roles) -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'task'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'userTask'{name = Name, module = Module, prompt = Prompt, roles = Roles}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {name,_} when Name==[] orelse is_atom(Name) -> Acc2; - {module,_} when Module==[] orelse is_atom(Module) -> Acc2; - {prompt,_} when is_list(Prompt) -> - lists:foldl(fun(Tmp, Acc3) when is_tuple(Tmp) -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{prompt, D}|Acc3] end, Acc2, Prompt); - {roles,_} when is_binary(Roles) -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'userTask'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'serviceTask'{name = Name, module = Module, prompt = Prompt, roles = Roles}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {name,_} when Name==[] orelse is_atom(Name) -> Acc2; - {module,_} when Module==[] orelse is_atom(Module) -> Acc2; - {prompt,_} when is_list(Prompt) -> - lists:foldl(fun(Tmp, Acc3) when is_tuple(Tmp) -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{prompt, D}|Acc3] end, Acc2, Prompt); - {roles,_} when is_binary(Roles) -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'serviceTask'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'receiveTask'{name = Name, module = Module, prompt = Prompt, roles = Roles}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {name,_} when Name==[] orelse is_atom(Name) -> Acc2; - {module,_} when Module==[] orelse is_atom(Module) -> Acc2; - {prompt,_} when is_list(Prompt) -> - lists:foldl(fun(Tmp, Acc3) when is_tuple(Tmp) -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{prompt, D}|Acc3] end, Acc2, Prompt); - {roles,_} when is_binary(Roles) -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'receiveTask'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'messageEvent'{name = Name, module = Module, prompt = Prompt, payload = Payload, timeout = Timeout}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {name,_} when Name==[] orelse is_atom(Name) -> Acc2; - {module,_} when Module==[] orelse is_atom(Module) -> Acc2; - {prompt,_} when is_list(Prompt) -> - lists:foldl(fun(Tmp, Acc3) when is_tuple(Tmp) -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{prompt, D}|Acc3] end, Acc2, Prompt); - {payload,_} when is_binary(Payload) -> Acc2; - {timeout,_} when is_tuple(Timeout) -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'messageEvent'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'boundaryEvent'{name = Name, module = Module, prompt = Prompt, payload = Payload, timeout = Timeout, timeDate = TimeDate, timeDuration = TimeDuration, timeCycle = TimeCycle}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {name,_} when Name==[] orelse is_atom(Name) -> Acc2; - {module,_} when Module==[] orelse is_atom(Module) -> Acc2; - {prompt,_} when is_list(Prompt) -> - lists:foldl(fun(Tmp, Acc3) when is_tuple(Tmp) -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{prompt, D}|Acc3] end, Acc2, Prompt); - {payload,_} when is_binary(Payload) -> Acc2; - {timeout,_} when is_tuple(Timeout) -> Acc2; - {timeDate,_} when is_binary(TimeDate) -> Acc2; - {timeDuration,_} when is_binary(TimeDuration) -> Acc2; - {timeCycle,_} when is_binary(TimeCycle) -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'boundaryEvent'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'timeoutEvent'{name = Name, module = Module, prompt = Prompt, payload = Payload, timeout = Timeout, timeDate = TimeDate, timeDuration = TimeDuration, timeCycle = TimeCycle}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {name,_} when Name==[] orelse is_atom(Name) -> Acc2; - {module,_} when Module==[] orelse is_atom(Module) -> Acc2; - {prompt,_} when is_list(Prompt) -> - lists:foldl(fun(Tmp, Acc3) when is_tuple(Tmp) -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{prompt, D}|Acc3] end, Acc2, Prompt); - {payload,_} when Payload==[] orelse is_binary(Payload) -> Acc2; - {timeout,_} when Timeout==[] orelse is_tuple(Timeout) -> Acc2; - {timeDate,_} when TimeDate==[] orelse is_binary(TimeDate) -> Acc2; - {timeDuration,_} when TimeDuration==[] orelse is_binary(TimeDuration) -> Acc2; - {timeCycle,_} when TimeCycle==[] orelse is_binary(TimeCycle) -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'timeoutEvent'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'beginEvent'{name = Name, module = Module, prompt = Prompt}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {name,_} when Name==[] orelse is_atom(Name) -> Acc2; - {module,_} when Module==[] orelse is_atom(Module) -> Acc2; - {prompt,_} when is_list(Prompt) -> - lists:foldl(fun(Tmp, Acc3) when is_tuple(Tmp) -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{prompt, D}|Acc3] end, Acc2, Prompt); - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'beginEvent'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'endEvent'{name = Name, module = Module, prompt = Prompt}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {name,_} when Name==[] orelse is_atom(Name) -> Acc2; - {module,_} when Module==[] orelse is_atom(Module) -> Acc2; - {prompt,_} when is_list(Prompt) -> - lists:foldl(fun(Tmp, Acc3) when is_tuple(Tmp) -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{prompt, D}|Acc3] end, Acc2, Prompt); - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'endEvent'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'sequenceFlow'{source = Source, target = Target}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {source,_} when Source==[] orelse is_atom(Source) -> Acc2; - {target,_} when Target==[] orelse is_atom(Target) orelse is_list(Target) -> - lists:foldl(fun(Tmp, Acc3) when is_atom(Tmp) -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{target, D}|Acc3] end, Acc2, Target); - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'sequenceFlow'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'hist'{id = Id, container = Container, feed_id = Feed_id, prev = Prev, next = Next, feeds = Feeds, name = Name, task = Task, docs = Docs, time = Time}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {id,_} when Id==[] orelse is_integer(Id) -> Acc2; - {container,_} when is_atom(Container) -> Acc2; - {feed_id,_} -> Acc2; - {prev,_} when Prev==[] orelse is_integer(Prev) -> Acc2; - {next,_} when Next==[] orelse is_integer(Next) -> Acc2; - {feeds,_} when is_list(Feeds) -> []; - {name,_} when Name==[] orelse is_binary(Name) -> Acc2; - {task,_} when is_atom(Task) -> Acc2; - {docs,_} when is_list(Docs) -> - lists:foldl(fun(Tmp, Acc3) when is_tuple(Tmp) -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{docs, D}|Acc3] end, Acc2, Docs); - {time,_} -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'hist'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'process'{id = Id, container = Container, feed_id = Feed_id, prev = Prev, next = Next, feeds = Feeds, name = Name, roles = Roles, tasks = Tasks, events = Events, hist = Hist, flows = Flows, rules = Rules, docs = Docs, options = Options, task = Task, timer = Timer, notifications = Notifications, result = Result, started = Started, beginEvent = BeginEvent, endEvent = EndEvent}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {id,_} when Id==[] orelse is_integer(Id) -> Acc2; - {container,_} when is_atom(Container) -> Acc2; - {feed_id,_} -> Acc2; - {prev,_} when Prev==[] orelse is_integer(Prev) -> Acc2; - {next,_} when Next==[] orelse is_integer(Next) -> Acc2; - {feeds,_} when is_list(Feeds) -> []; - {name,_} when Name==[] orelse is_binary(Name) -> Acc2; - {roles,_} when is_list(Roles) -> []; - {tasks,_} when is_list(Tasks) -> - lists:foldl(fun(Tmp, Acc3) when is_record(Tmp,'task') orelse is_record(Tmp,'serviceTask') orelse is_record(Tmp,'userTask') orelse is_record(Tmp,'receiveTask') -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{tasks, D}|Acc3] end, Acc2, Tasks); - {events,_} when is_list(Events) -> - lists:foldl(fun(Tmp, Acc3) when is_record(Tmp,'messageEvent') orelse is_record(Tmp,'boundaryEvent') orelse is_record(Tmp,'timeoutEvent') -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{events, D}|Acc3] end, Acc2, Events); - - {flows,_} when is_list(Flows) -> - lists:foldl(fun(Tmp, Acc3) when is_record(Tmp,'sequenceFlow') -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{flows, D}|Acc3] end, Acc2, Flows); - - {docs,_} when is_list(Docs) -> - lists:foldl(fun(Tmp, Acc3) when is_tuple(Tmp) -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{docs, D}|Acc3] end, Acc2, Docs); - {options,_} -> Acc2; - {task,_} when Task==[] orelse is_atom(Task) -> Acc2; - {timer,_} when Timer==[] orelse is_binary(Timer) -> Acc2; - {notifications,_} when Notifications==[] orelse true -> Acc2; - {result,_} when Result==[] orelse is_binary(Result) -> Acc2; - {started,_} when Started==[] orelse is_tuple(Started) -> Acc2; - {beginEvent,_} when BeginEvent==[] orelse is_atom(BeginEvent) -> Acc2; - {endEvent,_} when EndEvent==[] orelse is_atom(EndEvent) -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'process'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'complete'{id = Id}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {id,_} when Id==[] orelse is_integer(Id) -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'complete'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'proc'{id = Id}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {id,_} when Id==[] orelse is_integer(Id) -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'proc'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'load'{id = Id}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {id,_} when Id==[] orelse is_integer(Id) -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'load'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'histo'{id = Id}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {id,_} when Id==[] orelse is_integer(Id) -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'histo'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'create'{proc = Proc, docs = Docs}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {proc,_} when Proc==[] orelse is_record(Proc,'process') orelse is_binary(Proc) -> Acc2; - {docs,_} when Docs==[] orelse is_list(Docs) -> - lists:foldl(fun(Tmp, Acc3) when is_tuple(Tmp) -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{docs, D}|Acc3] end, Acc2, Docs); - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'create'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'amend'{id = Id, docs = Docs}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {id,_} when Id==[] orelse is_integer(Id) -> Acc2; - {docs,_} when Docs==[] orelse is_list(Docs) -> - lists:foldl(fun(Tmp, Acc3) when is_tuple(Tmp) -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{docs, D}|Acc3] end, Acc2, Docs); - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'amend'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'chain'{id = Id, top = Top, rear = Rear, count = Count}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {id,_} when Id==[] orelse is_integer(Id) -> Acc2; - {top,_} when Top==[] orelse is_integer(Top) -> Acc2; - {rear,_} when Rear==[] orelse is_integer(Rear) -> Acc2; - {count,_} when is_integer(Count) -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'chain'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'push'{model = Model, type = Type, title = Title, alert = Alert, badge = Badge, sound = Sound}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {model,_} when Model==[] orelse true -> Acc2; - {type,_} when Type==[] orelse is_binary(Type) -> Acc2; - {title,_} when Title==[] orelse is_binary(Title) -> Acc2; - {alert,_} when Alert==[] orelse is_binary(Alert) -> Acc2; - {badge,_} when Badge==[] orelse is_integer(Badge) -> Acc2; - {sound,_} when Sound==[] orelse is_binary(Sound) -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'push'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'Search'{id = Id, ref = Ref, field = Field, type = Type, value = Value, status = Status}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {id,_} when is_integer(Id) orelse is_binary(Id) -> Acc2; - {ref,_} when is_binary(Ref) -> Acc2; - {field,_} when is_binary(Field) -> Acc2; - {type,_} when Type=='==' orelse Type=='!=' orelse Type=='like' -> Acc2; - {value,_} when is_list(Value) -> - lists:foldl(fun(Tmp, Acc3) when true orelse is_binary(Tmp) -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{value, D}|Acc3] end, Acc2, Value); - {status,_} when Status=='profile' orelse Status=='roster' orelse Status=='contact' orelse Status=='member' orelse Status=='room' -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'Search'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'p2p'{from = From, to = To}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {from,_} when is_binary(From) -> Acc2; - {to,_} when is_binary(To) -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'p2p'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'muc'{name = Name}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {name,_} when is_binary(Name) -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'muc'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'mqi'{feed_id = Feed_id, query = Query, status = Status}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {feed_id, #'muc'{}} -> Acc2; - {query,_} when Query==[] orelse is_binary(Query) -> Acc2; - {status,_} when Status==[] orelse Status=='admin' orelse Status=='member' orelse Status=='removed' -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'mqi'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'Feature'{id = Id, key = Key, value = Value, group = Group}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {id,_} when Id==[] orelse is_binary(Id) -> Acc2; - {key,_} when is_binary(Key) -> Acc2; - {value,_} when is_binary(Value) -> Acc2; - {group,_} when is_binary(Group) -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'Feature'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'Service'{id = Id, type = Type, data = Data, login = Login, password = Password, expiration = Expiration, status = Status}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {id,_} when Id==[] orelse is_binary(Id) -> Acc2; - {type,_} when Type=='email' orelse Type=='vox' orelse Type=='aws' orelse Type=='wallet' -> Acc2; - {data,_} -> Acc2; - {login,_} when Login==[] orelse is_binary(Login) -> Acc2; - {password,_} when Password==[] orelse is_binary(Password) -> Acc2; - {expiration,_} when Expiration==[] orelse is_integer(Expiration) -> Acc2; - {status,_} when Status==[] orelse Status=='verified' orelse Status=='added' orelse Status=='add' orelse Status=='remove' -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'Service'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'Member'{id = Id, container = Container, feed_id = Feed_id, prev = Prev, next = Next, feeds = Feeds, phone_id = Phone_id, avatar = Avatar, names = Names, surnames = Surnames, alias = Alias, reader = Reader, update = Update, settings = Settings, services = Services, presence = Presence, status = Status}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {id,_} when Id==[] orelse is_integer(Id) -> Acc2; - {container,_} when Container=='chain' orelse Container=='cur' orelse Container==[] -> Acc2; - {feed_id,_} when is_record(Feed_id,'muc') orelse is_record(Feed_id,'p2p') orelse Feed_id==[] -> Acc2; - {prev,_} when Prev==[] orelse is_integer(Prev) -> Acc2; - {next,_} when Next==[] orelse is_integer(Next) -> Acc2; - {feeds,_} when is_list(Feeds) -> []; - {phone_id,_} when Phone_id==[] orelse is_binary(Phone_id) -> Acc2; - {avatar,_} when Avatar==[] orelse is_binary(Avatar) -> Acc2; - {names,_} when Names==[] orelse is_binary(Names) -> Acc2; - {surnames,_} when Surnames==[] orelse is_binary(Surnames) -> Acc2; - {alias,_} when Alias==[] orelse is_binary(Alias) -> Acc2; - {reader,_} when Reader==[] orelse is_integer(Reader) -> Acc2; - {update,_} when Update==[] orelse is_integer(Update) -> Acc2; - {settings,_} when Settings==[] orelse is_list(Settings) -> - lists:foldl(fun(Tmp, Acc3) when is_record(Tmp,'Feature') -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{settings, D}|Acc3] end, Acc2, Settings); - {services,_} when Services==[] orelse is_list(Services) -> - lists:foldl(fun(Tmp, Acc3) when is_record(Tmp,'Service') -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{services, D}|Acc3] end, Acc2, Services); - {presence,_} when Presence==[] orelse Presence=='online' orelse Presence=='offline' -> Acc2; - {status,_} when Status==[] orelse Status=='admin' orelse Status=='member' orelse Status=='removed' orelse Status=='patch' orelse Status=='owner' -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'Member'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'Desc'{id = Id, mime = Mime, payload = Payload, parentid = Parentid, data = Data}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {id,_} when Id==[] orelse is_binary(Id) -> Acc2; - {mime,_} when Mime==[] orelse is_binary(Mime) -> Acc2; - {payload,_} when Payload==[] orelse is_binary(Payload) -> Acc2; - {parentid,_} when Parentid==[] orelse is_binary(Parentid) -> Acc2; - {data,_} when is_list(Data) -> - lists:foldl(fun(Tmp, Acc3) when is_record(Tmp,'Feature') -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{data, D}|Acc3] end, Acc2, Data); - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'Desc'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'StickerPack'{id = Id, name = Name, keywords = Keywords, description = Description, author = Author, stickers = Stickers, created = Created, updated = Updated, downloaded = Downloaded}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {id,_} when Id==[] orelse is_integer(Id) -> Acc2; - {name,_} when Name==[] orelse is_binary(Name) -> Acc2; - {keywords,_} when is_list(Keywords) -> - lists:foldl(fun(Tmp, Acc3) when is_binary(Tmp) -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{keywords, D}|Acc3] end, Acc2, Keywords); - {description,_} when Description==[] orelse is_binary(Description) -> Acc2; - {author,_} when Author==[] orelse is_binary(Author) -> Acc2; - {stickers,_} when is_list(Stickers) -> - lists:foldl(fun(Tmp, Acc3) when is_record(Tmp,'Desc') -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{stickers, D}|Acc3] end, Acc2, Stickers); - {created,_} when is_integer(Created) -> Acc2; - {updated,_} when is_integer(Updated) -> Acc2; - {downloaded,_} when is_integer(Downloaded) -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'StickerPack'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'Message'{id = Id, container = Container, feed_id = Feed_id, prev = Prev, next = Next, msg_id = Msg_id, from = From, to = To, created = Created, files = Files, type = Type, link = Link, seenby = Seenby, repliedby = Repliedby, mentioned = Mentioned, status = Status}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {id,_} when Id==[] orelse is_integer(Id) -> Acc2; - {container,_} when Container=='chain' orelse Container=='cur' orelse Container==[] -> Acc2; - {feed_id,_} when is_record(Feed_id,'muc') orelse is_record(Feed_id,'p2p') -> Acc2; - {prev,_} when Prev==[] orelse is_integer(Prev) -> Acc2; - {next,_} when Next==[] orelse is_integer(Next) -> Acc2; - {msg_id,_} when Msg_id==[] orelse is_binary(Msg_id) -> Acc2; - {from,_} when From==[] orelse is_binary(From) -> Acc2; - {to,_} when To==[] orelse is_binary(To) -> Acc2; - {created,_} when Created==[] orelse is_integer(Created) -> Acc2; - {files,_} when is_list(Files) -> - lists:foldl(fun(Tmp, Acc3) when is_record(Tmp,'Desc') -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{files, D}|Acc3] end, Acc2, Files); - {type,_} when is_list(Type) -> - lists:foldl(fun(Tmp, Acc3) when Tmp=='sys' orelse Tmp=='reply' orelse Tmp=='forward' orelse Tmp=='read' orelse Tmp=='edited' orelse Tmp=='cursor' orelse Tmp=='draft' -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{type, D}|Acc3] end, Acc2, Type); - {link,_} when Link==[] orelse is_integer(Link) orelse is_record(Link,'Message') -> Acc2; - {seenby,_} when Seenby==[] orelse is_list(Seenby) -> - lists:foldl(fun(Tmp, Acc3) when is_binary(Tmp) orelse is_integer(Tmp) -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{seenby, D}|Acc3] end, Acc2, Seenby); - {repliedby,_} when Repliedby==[] orelse is_list(Repliedby) -> - lists:foldl(fun(Tmp, Acc3) when is_integer(Tmp) -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{repliedby, D}|Acc3] end, Acc2, Repliedby); - {mentioned,_} when Mentioned==[] orelse is_list(Mentioned) -> - lists:foldl(fun(Tmp, Acc3) when is_integer(Tmp) -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{mentioned, D}|Acc3] end, Acc2, Mentioned); - {status,_} when Status==[] orelse Status=='async' orelse Status=='delete' orelse Status=='clear' orelse Status=='update' orelse Status=='edit' -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'Message'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'Link'{id = Id, name = Name, room_id = Room_id, created = Created, type = Type, status = Status}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {id,_} when Id==[] orelse is_binary(Id) -> Acc2; - {name,_} when Name==[] orelse is_binary(Name) -> Acc2; - {room_id,_} when Room_id==[] orelse is_binary(Room_id) -> Acc2; - {created,_} when Created==[] orelse is_integer(Created) -> Acc2; - {type,_} when Type==[] orelse Type=='group' orelse Type=='channel' -> Acc2; - {status,_} when Status==[] orelse Status=='gen' orelse Status=='check' orelse Status=='add' orelse Status=='get' orelse Status=='join' orelse Status=='update' orelse Status=='delete' -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'Link'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'Room'{id = Id, name = Name, links = Links, description = Description, settings = Settings, members = Members, admins = Admins, data = Data, type = Type, tos = Tos, tos_update = Tos_update, unread = Unread, mentions = Mentions, readers = Readers, last_msg = Last_msg, update = Update, created = Created, status = Status}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {id,_} when Id==[] orelse is_binary(Id) -> Acc2; - {name,_} when Name==[] orelse is_binary(Name) -> Acc2; - {links,_} when Links==[] orelse is_list(Links) -> - lists:foldl(fun(Tmp, Acc3) when is_record(Tmp,'Link') -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{links, D}|Acc3] end, Acc2, Links); - {description,_} when Description==[] orelse is_binary(Description) -> Acc2; - {settings,_} when is_list(Settings) -> - lists:foldl(fun(Tmp, Acc3) when is_record(Tmp,'Feature') -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{settings, D}|Acc3] end, Acc2, Settings); - {members,_} when is_list(Members) -> - lists:foldl(fun(Tmp, Acc3) when is_record(Tmp,'Member') -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{members, D}|Acc3] end, Acc2, Members); - {admins,_} when is_list(Admins) -> - lists:foldl(fun(Tmp, Acc3) when is_record(Tmp,'Member') -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{admins, D}|Acc3] end, Acc2, Admins); - {data,_} when is_list(Data) -> - lists:foldl(fun(Tmp, Acc3) when is_record(Tmp,'Desc') -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{data, D}|Acc3] end, Acc2, Data); - {type,_} when Type==[] orelse Type=='group' orelse Type=='channel' orelse Type=='call' -> Acc2; - {tos,_} when Tos==[] orelse is_binary(Tos) -> Acc2; - {tos_update,_} when Tos_update==[] orelse is_integer(Tos_update) -> Acc2; - {unread,_} when Unread==[] orelse is_integer(Unread) -> Acc2; - {mentions,_} when is_list(Mentions) -> - lists:foldl(fun(Tmp, Acc3) when is_integer(Tmp) -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{mentions, D}|Acc3] end, Acc2, Mentions); - {readers,_} when is_list(Readers) -> - lists:foldl(fun(Tmp, Acc3) when is_integer(Tmp) -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{readers, D}|Acc3] end, Acc2, Readers); - {last_msg,_} when Last_msg==[] orelse is_integer(Last_msg) orelse is_record(Last_msg,'Message') -> Acc2; - {update,_} when Update==[] orelse is_integer(Update) -> Acc2; - {created,_} when Created==[] orelse is_integer(Created) -> Acc2; - {status,_} when Status==[] orelse Status=='create' orelse Status=='leave' orelse Status=='add' orelse Status=='remove' orelse Status=='removed' orelse Status=='join' orelse Status=='joined' orelse Status=='info' orelse Status=='patch' orelse Status=='get' orelse Status=='delete' orelse Status=='last_msg' orelse Status=='mute' orelse Status=='unmute' -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'Room'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'Tag'{roster_id = Roster_id, name = Name, color = Color, status = Status}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {roster_id,_} when Roster_id==[] orelse is_binary(Roster_id) -> Acc2; - {name,_} when is_binary(Name) -> Acc2; - {color,_} when is_binary(Color) -> Acc2; - {status,_} when Status==[] orelse Status=='create' orelse Status=='remove' orelse Status=='edit' -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'Tag'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'Star'{id = Id, client_id = Client_id, roster_id = Roster_id, message = Message, tags = Tags, status = Status}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {id,_} when Id==[] orelse is_integer(Id) -> Acc2; - {client_id,_} when Client_id==[] orelse is_binary(Client_id) -> Acc2; - {roster_id,_} when Roster_id==[] orelse is_integer(Roster_id) -> Acc2; - {message,_} when Message==[] orelse is_record(Message,'Message') -> Acc2; - {tags,_} when is_list(Tags) -> - lists:foldl(fun(Tmp, Acc3) when is_record(Tmp,'Tag') -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{tags, D}|Acc3] end, Acc2, Tags); - {status,_} when Status==[] orelse Status=='add' orelse Status=='remove' -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'Star'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'Typing'{phone_id = Phone_id, comments = Comments}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {phone_id,_} when is_binary(Phone_id) -> Acc2; - {comments,_} -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'Typing'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'Contact'{phone_id = Phone_id, avatar = Avatar, names = Names, surnames = Surnames, nick = Nick, reader = Reader, unread = Unread, last_msg = Last_msg, update = Update, created = Created, settings = Settings, services = Services, presence = Presence, status = Status}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {phone_id,_} when Phone_id==[] orelse is_binary(Phone_id) -> Acc2; - {avatar,_} when Avatar==[] orelse is_binary(Avatar) -> Acc2; - {names,_} when Names==[] orelse is_binary(Names) -> Acc2; - {surnames,_} when Surnames==[] orelse is_binary(Surnames) -> Acc2; - {nick,_} when Nick==[] orelse is_binary(Nick) -> Acc2; - {reader,_} when Reader==[] orelse is_integer(Reader) orelse is_list(Reader) -> - lists:foldl(fun(Tmp, Acc3) when is_integer(Tmp) -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{reader, D}|Acc3] end, Acc2, Reader); - {unread,_} when Unread==[] orelse is_integer(Unread) -> Acc2; - {last_msg,_} when Last_msg==[] orelse is_record(Last_msg,'Message') -> Acc2; - {update,_} when Update==[] orelse is_integer(Update) -> Acc2; - {created,_} when Created==[] orelse is_integer(Created) -> Acc2; - {settings,_} when is_list(Settings) -> - lists:foldl(fun(Tmp, Acc3) when is_record(Tmp,'Feature') -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{settings, D}|Acc3] end, Acc2, Settings); - {services,_} when is_list(Services) -> - lists:foldl(fun(Tmp, Acc3) when is_record(Tmp,'Service') -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{services, D}|Acc3] end, Acc2, Services); - {presence,_} when Presence==[] orelse Presence=='online' orelse Presence=='offline' orelse is_binary(Presence) -> Acc2; - {status,_} when Status==[] orelse Status=='request' orelse Status=='authorization' orelse Status=='ignore' orelse Status=='internal' orelse Status=='friend' orelse Status=='last_msg' orelse Status=='ban' orelse Status=='banned' orelse Status=='deleted' -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'Contact'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'ExtendedStar'{star = Star, from = From}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {star,_} when is_record(Star,'Star') orelse Star==[] -> Acc2; - {from,_} when is_record(From,'Contact') orelse is_record(From,'Room') orelse From==[] -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'ExtendedStar'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'Auth'{client_id = Client_id, dev_key = Dev_key, user_id = User_id, phone = Phone, token = Token, type = Type, sms_code = Sms_code, attempts = Attempts, services = Services, settings = Settings, push = Push, os = Os, created = Created, last_online = Last_online}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {client_id,_} when Client_id==[] orelse is_binary(Client_id) -> Acc2; - {dev_key,_} when Dev_key==[] orelse is_binary(Dev_key) -> Acc2; - {user_id,_} when User_id==[] orelse is_binary(User_id) -> Acc2; - {phone,_} when Phone==[] orelse is_binary(Phone) orelse is_tuple(Phone) -> Acc2; - {token,_} when Token==[] orelse is_binary(Token) -> Acc2; - {type,_} when Type==[] orelse is_atom(Type) -> Acc2; - {sms_code,_} when Sms_code==[] orelse is_binary(Sms_code) -> Acc2; - {attempts,_} when Attempts==[] orelse is_integer(Attempts) -> Acc2; - {services,_} when is_list(Services) -> - lists:foldl(fun(Tmp, Acc3) when true -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{services, D}|Acc3] end, Acc2, Services); - {settings,_} when Settings==[] orelse is_list(Settings) -> - lists:foldl(fun(Tmp, Acc3) when is_record(Tmp,'Feature') -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{settings, D}|Acc3] end, Acc2, Settings); - {push,_} when Push==[] orelse is_binary(Push) -> Acc2; - {os,_} when Os==[] orelse Os=='ios' orelse Os=='android' orelse Os=='web' -> Acc2; - {created,_} when Created==[] orelse is_integer(Created) -> Acc2; - {last_online,_} when Last_online==[] orelse is_integer(Last_online) -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'Auth'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'Roster'{id = Id, names = Names, surnames = Surnames, email = Email, nick = Nick, userlist = Userlist, roomlist = Roomlist, favorite = Favorite, tags = Tags, phone = Phone, avatar = Avatar, update = Update, status = Status}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {id,_} when Id==[] orelse is_binary(Id) orelse is_integer(Id) -> Acc2; - {names,_} when Names==[] orelse is_binary(Names) -> Acc2; - {surnames,_} when Surnames==[] orelse is_binary(Surnames) -> Acc2; - {email,_} when Email==[] orelse is_binary(Email) -> Acc2; - {nick,_} when Nick==[] orelse is_binary(Nick) -> Acc2; - {userlist,_} when is_list(Userlist) -> - lists:foldl(fun(Tmp, Acc3) when is_record(Tmp,'Contact') orelse is_integer(Tmp) -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{userlist, D}|Acc3] end, Acc2, Userlist); - {roomlist,_} when is_list(Roomlist) -> - lists:foldl(fun(Tmp, Acc3) when is_record(Tmp,'Room') -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{roomlist, D}|Acc3] end, Acc2, Roomlist); - {favorite,_} when is_list(Favorite) -> - lists:foldl(fun(Tmp, Acc3) when is_record(Tmp,'Star') -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{favorite, D}|Acc3] end, Acc2, Favorite); - {tags,_} when is_list(Tags) -> - lists:foldl(fun(Tmp, Acc3) when is_record(Tmp,'Tag') -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{tags, D}|Acc3] end, Acc2, Tags); - {phone,_} when Phone==[] orelse is_binary(Phone) -> Acc2; - {avatar,_} when Avatar==[] orelse is_binary(Avatar) -> Acc2; - {update,_} when Update==[] orelse is_integer(Update) -> Acc2; - {status,_} when Status==[] orelse Status=='get' orelse Status=='create' orelse Status=='del' orelse Status=='remove' orelse Status=='nick' orelse Status=='add' orelse Status=='update' orelse Status=='list' orelse Status=='patch' orelse Status=='last_msg' -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'Roster'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'Profile'{phone = Phone, services = Services, rosters = Rosters, settings = Settings, update = Update, balance = Balance, presence = Presence, status = Status}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {phone,_} when Phone==[] orelse is_binary(Phone) -> Acc2; - {services,_} when Services==[] orelse is_list(Services) -> - lists:foldl(fun(Tmp, Acc3) when is_record(Tmp,'Service') -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{services, D}|Acc3] end, Acc2, Services); - {rosters,_} when Rosters==[] orelse is_list(Rosters) -> - lists:foldl(fun(Tmp, Acc3) when is_record(Tmp,'Roster') orelse is_binary(Tmp) -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{rosters, D}|Acc3] end, Acc2, Rosters); - {settings,_} when Settings==[] orelse is_list(Settings) -> - lists:foldl(fun(Tmp, Acc3) when is_record(Tmp,'Feature') -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{settings, D}|Acc3] end, Acc2, Settings); - {update,_} when is_integer(Update) -> Acc2; - {balance,_} when is_integer(Balance) -> Acc2; - {presence,_} when Presence==[] orelse Presence=='offline' orelse Presence=='online' orelse is_binary(Presence) -> Acc2; - {status,_} when Status==[] orelse Status=='remove' orelse Status=='get' orelse Status=='patch' orelse Status=='update' orelse Status=='delete' orelse Status=='create' -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'Profile'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'Presence'{uid = Uid, status = Status}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {uid,_} when is_binary(Uid) -> Acc2; - {status,_} when Status==[] orelse Status=='offline' orelse Status=='online' orelse is_binary(Status) -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'Presence'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'Friend'{phone_id = Phone_id, friend_id = Friend_id, settings = Settings, status = Status}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {phone_id,_} when is_binary(Phone_id) -> Acc2; - {friend_id,_} when is_binary(Friend_id) -> Acc2; - {settings,_} when Settings==[] orelse is_list(Settings) -> - lists:foldl(fun(Tmp, Acc3) when is_record(Tmp,'Feature') -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{settings, D}|Acc3] end, Acc2, Settings); - {status,_} when Status=='ban' orelse Status=='unban' orelse Status=='request' orelse Status=='confirm' orelse Status=='update' orelse Status=='ignore' -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'Friend'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'act'{name = Name, data = Data}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {name,_} when Name==[] orelse is_binary(Name) -> Acc2; - {data,_} when is_binary(Data) orelse is_integer(Data) orelse is_list(Data) -> - lists:foldl(fun(Tmp, Acc3) when true -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{data, D}|Acc3] end, Acc2, Data); - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'act'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'Job'{id = Id, container = Container, feed_id = Feed_id, prev = Prev, next = Next, context = Context, proc = Proc, time = Time, data = Data, events = Events, settings = Settings, status = Status}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {id,_} when Id==[] orelse is_integer(Id) -> Acc2; - {container,_} when Container=='chain' orelse Container==[] -> Acc2; - {feed_id, #'act'{}} -> Acc2; - {prev,_} when Prev==[] orelse is_integer(Prev) -> Acc2; - {next,_} when Next==[] orelse is_integer(Next) -> Acc2; - {context,_} when Context==[] orelse is_integer(Context) orelse is_binary(Context) -> Acc2; - {proc,_} when Proc==[] orelse is_integer(Proc) orelse is_record(Proc,'process') -> Acc2; - {time,_} when Time==[] orelse is_integer(Time) -> Acc2; - {data,_} when Data==[] orelse is_binary(Data) orelse is_list(Data) -> - lists:foldl(fun(Tmp, Acc3) when true orelse is_record(Tmp,'Message') -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{data, D}|Acc3] end, Acc2, Data); - {events,_} when Events==[] orelse is_list(Events) -> - lists:foldl(fun(Tmp, Acc3) when is_record(Tmp,'messageEvent') -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{events, D}|Acc3] end, Acc2, Events); - {settings,_} when Settings==[] orelse is_list(Settings) -> - lists:foldl(fun(Tmp, Acc3) when is_record(Tmp,'Feature') -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{settings, D}|Acc3] end, Acc2, Settings); - {status,_} when Status==[] orelse Status=='init' orelse Status=='update' orelse Status=='delete' orelse Status=='pending' orelse Status=='stop' orelse Status=='complete' orelse Status=='restart' -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'Job'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'History'{roster_id = Roster_id, feed = Feed, size = Size, entity_id = Entity_id, data = Data, status = Status}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {roster_id,_} when is_binary(Roster_id) -> Acc2; - {feed,_} when is_record(Feed,'p2p') orelse is_record(Feed,'muc') orelse is_record(Feed,'act') orelse is_record(Feed,'StickerPack') orelse Feed==[] -> Acc2; - {size,_} when Size==[] orelse is_integer(Size) -> Acc2; - {entity_id,_} when Entity_id==[] orelse is_integer(Entity_id) -> Acc2; - {data,_} when Data==[] orelse is_list(Data) -> - lists:foldl(fun(Tmp, Acc3) when is_record(Tmp,'Message') orelse is_record(Tmp,'Job') orelse is_record(Tmp,'StickerPack') -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{data, D}|Acc3] end, Acc2, Data); - {status,_} when Status=='updated' orelse Status=='get' orelse Status=='update' orelse Status=='last_loaded' orelse Status=='last_msg' orelse Status=='get_reply' orelse Status=='double_get' orelse Status=='delete' orelse Status=='image' orelse Status=='video' orelse Status=='file' orelse Status=='link' orelse Status=='audio' orelse Status=='contact' orelse Status=='location' orelse Status=='text' orelse Status=='draft' -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'History'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'Schedule'{id = Id, proc = Proc, data = Data, state = State}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {id,_} when Id==[] orelse is_integer(Id) orelse is_tuple(Id) -> Acc2; - {proc,_} when Proc==[] orelse is_integer(Proc) orelse is_binary(Proc) -> Acc2; - {data,_} when is_binary(Data) orelse is_list(Data) -> - lists:foldl(fun(Tmp, Acc3) when true -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{data, D}|Acc3] end, Acc2, Data); - {state,_} when State==[] orelse true -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'Schedule'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'Index'{id = Id, roster = Roster}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {id,_} when Id==[] orelse true -> Acc2; - {roster,_} when is_list(Roster) -> - lists:foldl(fun(Tmp, Acc3) when true -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{roster, D}|Acc3] end, Acc2, Roster); - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'Index'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'Whitelist'{phone = Phone, created = Created}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {phone,_} when Phone==[] orelse is_binary(Phone) -> Acc2; - {created,_} when Created==[] orelse is_integer(Created) -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'Whitelist'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'error'{code = Code}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {code,_} when Code==[] orelse is_atom(Code) -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'error'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'ok'{code = Code}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {code,_} when Code==[] orelse is_binary(Code) -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'ok'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'error2'{code = Code, src = Src}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {code,_} when Code==[] orelse is_atom(Code) -> Acc2; - {src,_} when Src==[] orelse is_binary(Src) orelse is_integer(Src) -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'error2'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'ok2'{code = Code, src = Src}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {code,_} when Code==[] orelse is_atom(Code) -> Acc2; - {src,_} when Src==[] orelse is_tuple(Src) orelse is_binary(Src) -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'ok2'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'io'{code = Code, data = Data}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {code,_} when Code==[] orelse is_record(Code,'ok') orelse is_record(Code,'error') orelse is_record(Code,'ok2') orelse is_record(Code,'error2') orelse Code=='transcribe' -> Acc2; - {data,_} when Data==[] orelse is_binary(Data) orelse is_record(Data,'Roster') orelse is_tuple(Data) -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'io'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'Ack'{table = Table, id = Id}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {table,_} when is_atom(Table) -> Acc2; - {id,_} when Id==[] orelse is_binary(Id) -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'Ack'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'errors'{code = Code, data = Data}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {code,_} when Code==[] orelse is_list(Code) -> - lists:foldl(fun(Tmp, Acc3) when is_binary(Tmp) -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{code, D}|Acc3] end, Acc2, Code); - {data,_} when Data==[] orelse true -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'errors'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'PushService'{recipients = Recipients, id = Id, ttl = Ttl, module = Module, priority = Priority, payload = Payload}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {recipients,_} when is_list(Recipients) -> - lists:foldl(fun(Tmp, Acc3) when is_binary(Tmp) -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{recipients, D}|Acc3] end, Acc2, Recipients); - {id,_} when Id==[] orelse is_binary(Id) -> Acc2; - {ttl,_} when Ttl==[] orelse is_integer(Ttl) -> Acc2; - {module,_} when Module==[] orelse is_binary(Module) -> Acc2; - {priority,_} when Priority==[] orelse is_binary(Priority) -> Acc2; - {payload,_} when Payload==[] orelse is_binary(Payload) -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'PushService'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'PublishService'{message = Message, topic = Topic, qos = Qos}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {message,_} when Message==[] orelse is_binary(Message) -> Acc2; - {topic,_} when Topic==[] orelse is_binary(Topic) -> Acc2; - {qos,_} when Qos==[] orelse is_integer(Qos) -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'PublishService'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'FakeNumbers'{phone = Phone, created = Created}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {phone,_} when Phone==[] orelse is_binary(Phone) -> Acc2; - {created,_} when Created==[] orelse is_integer(Created) -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'FakeNumbers'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'Draft'{data = Data, status = Status}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {data,_} when is_list(Data) -> - lists:foldl(fun(Tmp, Acc3) when true -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{data, D}|Acc3] end, Acc2, Data); - {status,_} when Status=='update' orelse Status=='get' orelse Status=='delete' -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'Draft'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'handler'{name = Name, module = Module, class = Class, group = Group, config = Config, state = State, seq = Seq}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {name,_} when is_atom(Name) -> Acc2; - {module,_} when is_atom(Module) -> Acc2; - {class,_} -> Acc2; - {group,_} when is_atom(Group) -> Acc2; - {config,_} -> Acc2; - {state,_} -> Acc2; - {seq,_} -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'handler'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'pi'{name = Name, sup = Sup, module = Module, state = State}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {name,_} when is_atom(Name) -> Acc2; - {sup,_} when is_atom(Sup) -> Acc2; - {module,_} when is_atom(Module) -> Acc2; - {state,_} -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'pi'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'cx'{handlers = Handlers, actions = Actions, req = Req, module = Module, lang = Lang, path = Path, session = Session, formatter = Formatter, params = Params, node = Node, client_pid = Client_pid, state = State, from = From, vsn = Vsn}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {handlers,_} when is_list(Handlers) -> - lists:foldl(fun(Tmp, Acc3) when is_record(Tmp,'handler') -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{handlers, D}|Acc3] end, Acc2, Handlers); - {actions,_} when is_list(Actions) -> - lists:foldl(fun(Tmp, Acc3) when is_tuple(Tmp) -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{actions, D}|Acc3] end, Acc2, Actions); - {req,_} when Req==[] orelse true -> Acc2; - {module,_} when Module==[] orelse is_atom(Module) -> Acc2; - {lang,_} when Lang==[] orelse is_atom(Lang) -> Acc2; - {path,_} when Path==[] orelse is_binary(Path) -> Acc2; - {session,_} when Session==[] orelse is_binary(Session) -> Acc2; - {formatter,_} when Formatter=='bert' orelse Formatter=='json' -> Acc2; - {params,_} when Params==[] orelse is_list(Params) -> - lists:foldl(fun(Tmp, Acc3) when is_tuple(Tmp) -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{params, D}|Acc3] end, Acc2, Params); - {node,_} when Node==[] orelse is_atom(Node) -> Acc2; - {client_pid,_} when Client_pid==[] orelse true -> Acc2; - {state,_} when State==[] orelse true -> Acc2; - {from,_} when From==[] orelse is_binary(From) -> Acc2; - {vsn,_} when Vsn==[] orelse is_binary(Vsn) -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'cx'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'user'{id = Id, container = Container, feed_id = Feed_id, prev = Prev, next = Next, feeds = Feeds, email = Email}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {id,_} when Id==[] orelse is_integer(Id) -> Acc2; - {container,_} when is_atom(Container) -> Acc2; - {feed_id,_} -> Acc2; - {prev,_} when Prev==[] orelse is_integer(Prev) -> Acc2; - {next,_} when Next==[] orelse is_integer(Next) -> Acc2; - {feeds,_} when is_list(Feeds) -> []; - {email,_} when Email==[] orelse is_binary(Email) -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'user'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'group'{id = Id, container = Container, feed_id = Feed_id, prev = Prev, next = Next, feeds = Feeds}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {id,_} when Id==[] orelse is_integer(Id) -> Acc2; - {container,_} when is_atom(Container) -> Acc2; - {feed_id,_} -> Acc2; - {prev,_} when Prev==[] orelse is_integer(Prev) -> Acc2; - {next,_} when Next==[] orelse is_integer(Next) -> Acc2; - {feeds,_} when is_list(Feeds) -> []; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'group'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'mqtt_topic'{topic = Topic, flags = Flags}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {topic,_} when is_binary(Topic) -> Acc2; - {flags,_} when is_list(Flags) -> - lists:foldl(fun(Tmp, Acc3) when Tmp=='retained' orelse Tmp=='static' -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{flags, D}|Acc3] end, Acc2, Flags); - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'mqtt_topic'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'mqtt_client'{client_id = Client_id, client_pid = Client_pid, username = Username, peername = Peername, clean_sess = Clean_sess, proto_ver = Proto_ver, will_topic = Will_topic, ws_initial_headers = Ws_initial_headers}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {client_id,_} when is_binary(Client_id) orelse Client_id=='undefined' -> Acc2; - - {username,_} when is_binary(Username) orelse Username=='undefined' -> Acc2; - {peername,_} when is_tuple(Peername) -> Acc2; - - {proto_ver,_} when Proto_ver==3 orelse Proto_ver==4 -> Acc2; - {will_topic,_} when Will_topic=='undefined' orelse is_binary(Will_topic) -> Acc2; - {ws_initial_headers,_} when is_list(Ws_initial_headers) -> - lists:foldl(fun(Tmp, Acc3) when is_tuple(Tmp) -> validate(Tmp, Acc3, CM); (Tmp, Acc3) -> [{ws_initial_headers, D}|Acc3] end, Acc2, Ws_initial_headers); - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'mqtt_client'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'mqtt_session'{client_id = Client_id, sess_pid = Sess_pid, clean_sess = Clean_sess}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {client_id,_} when is_binary(Client_id) -> Acc2; - - - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'mqtt_session'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'mqtt_message'{id = Id}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {id,_} when is_binary(Id) orelse Id=='undefined' -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'mqtt_message'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'mqtt_delivery'{sender = Sender, message = Message}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - - {message, #'mqtt_message'{}} -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'mqtt_delivery'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'mqtt_route'{topic = Topic, node = Node}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {topic,_} when is_binary(Topic) -> Acc2; - - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'mqtt_route'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end; -validate(D = #'mqtt_alarm'{id = Id, severity = Severity, title = Title, summary = Summary}, Acc, {CustomValidateModule, ValidateFun} = CM) -> - ErrFields = lists:foldl(fun ({RecField, F}, Acc2) -> - case {RecField, F} of - {id,_} when is_binary(Id) -> Acc2; - {severity,_} when Severity=='warning' orelse Severity=='error' orelse Severity=='critical' -> Acc2; - {title,_} when is_list(Title) orelse is_binary(Title) -> Acc2; - {summary,_} when is_list(Summary) orelse is_binary(Summary) -> Acc2; - _ -> [{RecField, D}|Acc2] - end end, Acc, lists:zip(record_info(fields, 'mqtt_alarm'), tl(tuple_to_list(D)))), - ErrFields++case ErrFields of [] -> CustomValidateModule:ValidateFun(D); _ -> [] end. \ No newline at end of file +validate(D = #'writer'{count = Count, cache = Cache, first = First}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {id,_} -> []; + {count,_} when is_integer(Count) -> []; + {cache,_} when (is_tuple(Cache) orelse Cache==[]) -> []; + {args,_} -> []; + {first,_} when (is_tuple(First) orelse First==[]) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'writer'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], + case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'reader'{pos = Pos, cache = Cache}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {id,_} -> []; + {pos,_} when (is_integer(Pos) orelse Pos==[]) -> []; + {cache,_} when (is_integer(Cache) orelse Cache==[]) -> []; + {args,_} -> []; + {feed,_} -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'reader'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], + case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'iter'{id = Id, container = Container, next = Next, prev = Prev}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {id,_} when (is_integer(Id) orelse Id==[]) -> []; + {container,_} when is_atom(Container) -> []; + {feed,_} -> []; + {next,_} when (is_integer(Next) orelse Next==[]) -> []; + {prev,_} when (is_integer(Prev) orelse Prev==[]) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'iter'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], + case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'container'{id = Id, top = Top, rear = Rear, count = Count}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {id,_} when (is_integer(Id) orelse Id==[]) -> []; + {top,_} when (is_integer(Top) orelse Top==[]) -> []; + {rear,_} when (is_integer(Rear) orelse Rear==[]) -> []; + {count,_} when is_integer(Count) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'container'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], + case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'iterator'{id = Id, container = Container, prev = Prev, next = Next, feeds = Feeds}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {id,_} when (is_integer(Id) orelse Id==[]) -> []; + {container,_} when is_atom(Container) -> []; + {feed_id,_} -> []; + {prev,_} when (is_integer(Prev) orelse Prev==[]) -> []; + {next,_} when (is_integer(Next) orelse Next==[]) -> []; + {feeds,_} when is_list(Feeds) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'iterator'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], + case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'task'{name = Name, module = Module, prompt = Prompt, roles = Roles}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {name,_} when (is_atom(Name) orelse Name==[]) -> []; + {module,_} when (is_atom(Module) orelse Module==[]) -> []; + {prompt,_} when is_list(Prompt) -> []; + {roles,_} when is_binary(Roles) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'task'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], + case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'userTask'{name = Name, module = Module, prompt = Prompt, roles = Roles}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {name,_} when (is_atom(Name) orelse Name==[]) -> []; + {module,_} when (is_atom(Module) orelse Module==[]) -> []; + {prompt,_} when is_list(Prompt) -> []; + {roles,_} when is_binary(Roles) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'userTask'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], + case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'serviceTask'{name = Name, module = Module, prompt = Prompt, roles = Roles}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {name,_} when (is_atom(Name) orelse Name==[]) -> []; + {module,_} when (is_atom(Module) orelse Module==[]) -> []; + {prompt,_} when is_list(Prompt) -> []; + {roles,_} when is_binary(Roles) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'serviceTask'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], + case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'receiveTask'{name = Name, module = Module, prompt = Prompt, roles = Roles}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {name,_} when (is_atom(Name) orelse Name==[]) -> []; + {module,_} when (is_atom(Module) orelse Module==[]) -> []; + {prompt,_} when is_list(Prompt) -> []; + {roles,_} when is_binary(Roles) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'receiveTask'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], + case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'messageEvent'{name = Name, module = Module, prompt = Prompt, payload = Payload, timeout = Timeout}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {name,_} when (is_atom(Name) orelse Name==[]) -> []; + {module,_} when (is_atom(Module) orelse Module==[]) -> []; + {prompt,_} when is_list(Prompt) -> []; + {payload,_} when is_binary(Payload) -> []; + {timeout,_} when is_tuple(Timeout) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'messageEvent'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], + case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'boundaryEvent'{name = Name, module = Module, prompt = Prompt, payload = Payload, timeout = Timeout, timeDate = TimeDate, timeDuration = TimeDuration, timeCycle = TimeCycle}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {name,_} when (is_atom(Name) orelse Name==[]) -> []; + {module,_} when (is_atom(Module) orelse Module==[]) -> []; + {prompt,_} when is_list(Prompt) -> []; + {payload,_} when is_binary(Payload) -> []; + {timeout,_} when is_tuple(Timeout) -> []; + {timeDate,_} when is_binary(TimeDate) -> []; + {timeDuration,_} when is_binary(TimeDuration) -> []; + {timeCycle,_} when is_binary(TimeCycle) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'boundaryEvent'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], + case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'timeoutEvent'{name = Name, module = Module, prompt = Prompt, payload = Payload, timeout = Timeout, timeDate = TimeDate, timeDuration = TimeDuration, timeCycle = TimeCycle}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {name,_} when (is_atom(Name) orelse Name==[]) -> []; + {module,_} when (is_atom(Module) orelse Module==[]) -> []; + {prompt,_} when is_list(Prompt) -> []; + {payload,_} when (is_binary(Payload) orelse Payload==[]) -> []; + {timeout,_} when (is_tuple(Timeout) orelse Timeout==[]) -> []; + {timeDate,_} when (is_binary(TimeDate) orelse TimeDate==[]) -> []; + {timeDuration,_} when (is_binary(TimeDuration) orelse TimeDuration==[]) -> []; + {timeCycle,_} when (is_binary(TimeCycle) orelse TimeCycle==[]) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'timeoutEvent'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], + case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'beginEvent'{name = Name, module = Module, prompt = Prompt}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {name,_} when (is_atom(Name) orelse Name==[]) -> []; + {module,_} when (is_atom(Module) orelse Module==[]) -> []; + {prompt,_} when is_list(Prompt) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'beginEvent'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], + case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'endEvent'{name = Name, module = Module, prompt = Prompt}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {name,_} when (is_atom(Name) orelse Name==[]) -> []; + {module,_} when (is_atom(Module) orelse Module==[]) -> []; + {prompt,_} when is_list(Prompt) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'endEvent'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], + case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'sequenceFlow'{source = Source, target = Target}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {source,_} when (is_atom(Source) orelse Source==[]) -> []; + {target,_} when (is_list(Target) orelse is_atom(Target) orelse Target==[]) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'sequenceFlow'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], + case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'hist'{id = Id, container = Container, prev = Prev, next = Next, feeds = Feeds, name = Name, task = Task, docs = Docs}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {id,_} when (is_integer(Id) orelse Id==[]) -> []; + {container,_} when is_atom(Container) -> []; + {feed_id,_} -> []; + {prev,_} when (is_integer(Prev) orelse Prev==[]) -> []; + {next,_} when (is_integer(Next) orelse Next==[]) -> []; + {feeds,_} when is_list(Feeds) -> []; + {name,_} when (is_binary(Name) orelse Name==[]) -> []; + {task,_} when is_atom(Task) -> []; + {docs,_} when is_list(Docs) -> []; + {time,_} -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'hist'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], + case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'process'{id = Id, container = Container, prev = Prev, next = Next, feeds = Feeds, name = Name, roles = Roles, tasks = Tasks, events = Events, flows = Flows, docs = Docs, task = Task, timer = Timer, notifications = Notifications, result = Result, started = Started, beginEvent = BeginEvent, endEvent = EndEvent}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {id,_} when (is_integer(Id) orelse Id==[]) -> []; + {container,_} when is_atom(Container) -> []; + {feed_id,_} -> []; + {prev,_} when (is_integer(Prev) orelse Prev==[]) -> []; + {next,_} when (is_integer(Next) orelse Next==[]) -> []; + {feeds,_} when is_list(Feeds) -> []; + {name,_} when (is_binary(Name) orelse Name==[]) -> []; + {roles,_} when is_list(Roles) -> []; + {tasks,_} when is_list(Tasks) -> []; + {events,_} when is_list(Events) -> []; + {flows,_} when is_list(Flows) -> []; + {docs,_} when is_list(Docs) -> []; + {options,_} -> []; + {task,_} when (is_atom(Task) orelse Task==[]) -> []; + {timer,_} when (is_binary(Timer) orelse Timer==[]) -> []; + {notifications,_} when (Notifications==[]) -> []; + {result,_} when (is_binary(Result) orelse Result==[]) -> []; + {started,_} when (is_tuple(Started) orelse Started==[]) -> []; + {beginEvent,_} when (is_atom(BeginEvent) orelse BeginEvent==[]) -> []; + {endEvent,_} when (is_atom(EndEvent) orelse EndEvent==[]) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'process'), tl(tuple_to_list(D)))]), + CondFuns = [?COND_FUN(is_record(Rec, 'sequenceFlow'))], + Fields = [Flows], + FieldNames = [flows], case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'complete'{id = Id}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {id,_} when (is_integer(Id) orelse Id==[]) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'complete'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], + case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'proc'{id = Id}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {id,_} when (is_integer(Id) orelse Id==[]) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'proc'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], + case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'load'{id = Id}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {id,_} when (is_integer(Id) orelse Id==[]) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'load'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], + case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'histo'{id = Id}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {id,_} when (is_integer(Id) orelse Id==[]) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'histo'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], + case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'create'{proc = Proc, docs = Docs}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {proc,_} when (is_binary(Proc) orelse is_record(Proc,'process') orelse Proc==[]) -> []; + {docs,_} when (is_list(Docs) orelse Docs==[]) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'create'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'amend'{id = Id, docs = Docs}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {id,_} when (is_integer(Id) orelse Id==[]) -> []; + {docs,_} when (is_list(Docs) orelse Docs==[]) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'amend'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], + case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'push'{model = Model, type = Type, title = Title, alert = Alert, badge = Badge, sound = Sound}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {model,_} when (Model==[]) -> []; + {type,_} when (is_binary(Type) orelse Type==[]) -> []; + {title,_} when (is_binary(Title) orelse Title==[]) -> []; + {alert,_} when (is_binary(Alert) orelse Alert==[]) -> []; + {badge,_} when (is_integer(Badge) orelse Badge==[]) -> []; + {sound,_} when (is_binary(Sound) orelse Sound==[]) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'push'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], + case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'Search'{id = Id, ref = Ref, type = Type, value = Value, status = Status}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {id,_} when (is_binary(Id) orelse is_integer(Id)) -> []; + {ref,_} when is_binary(Ref) -> []; + {field,_} when is_binary(Field) -> []; + {type,_} when (Type=='like' orelse Type=='!=' orelse Type=='==') -> []; + {value,_} when is_list(Value) -> []; + {status,_} when (Status=='room' orelse Status=='member' orelse Status=='contact' orelse Status=='roster' orelse Status=='profile') -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'Search'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'p2p'{from = From, to = To}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {from,_} when is_binary(From) -> []; + {to,_} when is_binary(To) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'p2p'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], + case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'muc'{name = Name}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {name,_} when is_binary(Name) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'muc'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], + case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'mqi'{feed_id = Feed_id, query = Query, status = Status}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {feed_id,_} when is_record(Feed_id,'mqi') -> []; + {query,_} when (is_binary(Query) orelse Query==[]) -> []; + {status,_} when (Status=='removed' orelse Status=='member' orelse Status=='admin' orelse Status==[]) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'mqi'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], + case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'Feature'{id = Id, key = Key, value = Value, group = Group}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {id,_} when (is_binary(Id) orelse Id==[]) -> []; + {key,_} when is_binary(Key) -> []; + {value,_} when is_binary(Value) -> []; + {group,_} when is_binary(Group) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'Feature'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], + case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'Service'{id = Id, type = Type, login = Login, password = Password, expiration = Expiration, status = Status}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {id,_} when (is_binary(Id) orelse Id==[]) -> []; + {type,_} when (Type=='wallet' orelse Type=='aws' orelse Type=='vox' orelse Type=='email') -> []; + {data,_} -> []; + {login,_} when (is_binary(Login) orelse Login==[]) -> []; + {password,_} when (is_binary(Password) orelse Password==[]) -> []; + {expiration,_} when (is_integer(Expiration) orelse Expiration==[]) -> []; + {status,_} when (Status=='remove' orelse Status=='add' orelse Status=='added' orelse Status=='verified' orelse Status==[]) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'Service'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], + case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'Member'{id = Id, container = Container, feed_id = Feed_id, prev = Prev, next = Next, feeds = Feeds, phone_id = Phone_id, avatar = Avatar, names = Names, surnames = Surnames, alias = Alias, reader = Reader, update = Update, settings = Settings, services = Services, presence = Presence, status = Status}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {id,_} when (is_integer(Id) orelse Id==[]) -> []; + {container,_} when (Container==[] orelse Container=='cur' orelse Container=='chain') -> []; + {feed_id,_} when (Feed_id==[] orelse is_record(Feed_id,'p2p') orelse is_record(Feed_id,'muc')) -> []; + {prev,_} when (is_integer(Prev) orelse Prev==[]) -> []; + {next,_} when (is_integer(Next) orelse Next==[]) -> []; + {feeds,_} when is_list(Feeds) -> []; + {phone_id,_} when (is_binary(Phone_id) orelse Phone_id==[]) -> []; + {avatar,_} when (is_binary(Avatar) orelse Avatar==[]) -> []; + {names,_} when (is_binary(Names) orelse Names==[]) -> []; + {surnames,_} when (is_binary(Surnames) orelse Surnames==[]) -> []; + {alias,_} when (is_binary(Alias) orelse Alias==[]) -> []; + {reader,_} when (is_integer(Reader) orelse Reader==[]) -> []; + {update,_} when (is_integer(Update) orelse Update==[]) -> []; + {settings,_} when (is_list(Settings) orelse Settings==[]) -> []; + {services,_} when (is_list(Services) orelse Services==[]) -> []; + {presence,_} when (Presence=='offline' orelse Presence=='online' orelse Presence==[]) -> []; + {status,_} when (Status=='owner' orelse Status=='patch' orelse Status=='removed' orelse Status=='member' orelse Status=='admin' orelse Status==[]) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'Member'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'Desc'{id = Id, mime = Mime, payload = Payload, parentid = Parentid, data = Data}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {id,_} when (is_binary(Id) orelse Id==[]) -> []; + {mime,_} when (is_binary(Mime) orelse Mime==[]) -> []; + {payload,_} when (is_binary(Payload) orelse Payload==[]) -> []; + {parentid,_} when (is_binary(Parentid) orelse Parentid==[]) -> []; + {data,_} when is_list(Data) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'Desc'), tl(tuple_to_list(D)))]), + CondFuns = [?COND_FUN(is_record(Rec, 'Feature'))], + Fields = [Data], + FieldNames = [data], case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'StickerPack'{id = Id, name = Name, keywords = Keywords, description = Description, author = Author, stickers = Stickers, created = Created, updated = Updated, downloaded = Downloaded}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {id,_} when (is_integer(Id) orelse Id==[]) -> []; + {name,_} when (is_binary(Name) orelse Name==[]) -> []; + {keywords,_} when is_list(Keywords) -> []; + {description,_} when (is_binary(Description) orelse Description==[]) -> []; + {author,_} when (is_binary(Author) orelse Author==[]) -> []; + {stickers,_} when is_list(Stickers) -> []; + {created,_} when is_integer(Created) -> []; + {updated,_} when is_integer(Updated) -> []; + {downloaded,_} when is_integer(Downloaded) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'StickerPack'), tl(tuple_to_list(D)))]), + CondFuns = [?COND_FUN(is_record(Rec, 'Desc'))], + Fields = [Stickers], + FieldNames = [stickers], case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'Message'{id = Id, container = Container, feed_id = Feed_id, prev = Prev, next = Next, msg_id = Msg_id, from = From, to = To, created = Created, files = Files, type = Type, link = Link, seenby = Seenby, repliedby = Repliedby, mentioned = Mentioned, status = Status}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {id,_} when (is_integer(Id) orelse Id==[]) -> []; + {container,_} when (Container==[] orelse Container=='cur' orelse Container=='chain') -> []; + {feed_id,_} when (is_record(Feed_id,'p2p') orelse is_record(Feed_id,'muc')) -> []; + {prev,_} when (is_integer(Prev) orelse Prev==[]) -> []; + {next,_} when (is_integer(Next) orelse Next==[]) -> []; + {msg_id,_} when (is_binary(Msg_id) orelse Msg_id==[]) -> []; + {from,_} when (is_binary(From) orelse From==[]) -> []; + {to,_} when (is_binary(To) orelse To==[]) -> []; + {created,_} when (is_integer(Created) orelse Created==[]) -> []; + {files,_} when is_list(Files) -> []; + {type,_} when is_list(Type) -> []; + {link,_} when (is_record(Link,'Message') orelse is_integer(Link) orelse Link==[]) -> []; + {seenby,_} when (is_list(Seenby) orelse Seenby==[]) -> []; + {repliedby,_} when (is_list(Repliedby) orelse Repliedby==[]) -> []; + {mentioned,_} when (is_list(Mentioned) orelse Mentioned==[]) -> []; + {status,_} when (Status=='edit' orelse Status=='update' orelse Status=='clear' orelse Status=='delete' orelse Status=='async' orelse Status==[]) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'Message'), tl(tuple_to_list(D)))]), + CondFuns = [?COND_FUN(is_record(Rec, 'Desc'))], + Fields = [Files], + FieldNames = [files], case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'Link'{id = Id, name = Name, room_id = Room_id, created = Created, type = Type, status = Status}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {id,_} when (is_binary(Id) orelse Id==[]) -> []; + {name,_} when (is_binary(Name) orelse Name==[]) -> []; + {room_id,_} when (is_binary(Room_id) orelse Room_id==[]) -> []; + {created,_} when (is_integer(Created) orelse Created==[]) -> []; + {type,_} when (Type=='channel' orelse Type=='group' orelse Type==[]) -> []; + {status,_} when (Status=='delete' orelse Status=='update' orelse Status=='join' orelse Status=='get' orelse Status=='add' orelse Status=='check' orelse Status=='gen' orelse Status==[]) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'Link'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], + case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'Room'{id = Id, name = Name, links = Links, description = Description, settings = Settings, members = Members, admins = Admins, data = Data, type = Type, tos = Tos, tos_update = Tos_update, unread = Unread, mentions = Mentions, readers = Readers, last_msg = Last_msg, update = Update, created = Created, status = Status}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {id,_} when (is_binary(Id) orelse Id==[]) -> []; + {name,_} when (is_binary(Name) orelse Name==[]) -> []; + {links,_} when (is_list(Links) orelse Links==[]) -> []; + {description,_} when (is_binary(Description) orelse Description==[]) -> []; + {settings,_} when is_list(Settings) -> []; + {members,_} when is_list(Members) -> []; + {admins,_} when is_list(Admins) -> []; + {data,_} when is_list(Data) -> []; + {type,_} when (Type=='call' orelse Type=='channel' orelse Type=='group' orelse Type==[]) -> []; + {tos,_} when (is_binary(Tos) orelse Tos==[]) -> []; + {tos_update,_} when (is_integer(Tos_update) orelse Tos_update==[]) -> []; + {unread,_} when (is_integer(Unread) orelse Unread==[]) -> []; + {mentions,_} when is_list(Mentions) -> []; + {readers,_} when is_list(Readers) -> []; + {last_msg,_} when (is_record(Last_msg,'Message') orelse is_integer(Last_msg) orelse Last_msg==[]) -> []; + {update,_} when (is_integer(Update) orelse Update==[]) -> []; + {created,_} when (is_integer(Created) orelse Created==[]) -> []; + {status,_} when (Status=='unmute' orelse Status=='mute' orelse Status=='last_msg' orelse Status=='delete' orelse Status=='get' orelse Status=='patch' orelse Status=='info' orelse Status=='joined' orelse Status=='join' orelse Status=='removed' orelse Status=='remove' orelse Status=='add' orelse Status=='leave' orelse Status=='create' orelse Status==[]) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'Room'), tl(tuple_to_list(D)))]), + CondFuns = [?COND_FUN(is_record(Rec, 'Feature')),?COND_FUN(is_record(Rec, 'Member')),?COND_FUN(is_record(Rec, 'Member')),?COND_FUN(is_record(Rec, 'Desc'))], + Fields = [Settings,Members,Admins,Data], + FieldNames = [settings,members,admins,data], case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'Tag'{roster_id = Roster_id, name = Name, color = Color, status = Status}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {roster_id,_} when (is_binary(Roster_id) orelse Roster_id==[]) -> []; + {name,_} when is_binary(Name) -> []; + {color,_} when is_binary(Color) -> []; + {status,_} when (Status=='edit' orelse Status=='remove' orelse Status=='create' orelse Status==[]) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'Tag'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], + case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'Star'{id = Id, client_id = Client_id, roster_id = Roster_id, message = Message, tags = Tags, status = Status}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {id,_} when (is_integer(Id) orelse Id==[]) -> []; + {client_id,_} when (is_binary(Client_id) orelse Client_id==[]) -> []; + {roster_id,_} when (is_integer(Roster_id) orelse Roster_id==[]) -> []; + {message,_} when (is_record(Message,'Message') orelse Message==[]) -> []; + {tags,_} when is_list(Tags) -> []; + {status,_} when (Status=='remove' orelse Status=='add' orelse Status==[]) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'Star'), tl(tuple_to_list(D)))]), + CondFuns = [?COND_FUN(is_record(Rec, 'Tag'))], + Fields = [Tags], + FieldNames = [tags], case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'Typing'{phone_id = Phone_id}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {phone_id,_} when is_binary(Phone_id) -> []; + {comments,_} -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'Typing'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], + case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'Contact'{phone_id = Phone_id, avatar = Avatar, names = Names, surnames = Surnames, nick = Nick, reader = Reader, unread = Unread, last_msg = Last_msg, update = Update, created = Created, settings = Settings, services = Services, presence = Presence, status = Status}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {phone_id,_} when (is_binary(Phone_id) orelse Phone_id==[]) -> []; + {avatar,_} when (is_binary(Avatar) orelse Avatar==[]) -> []; + {names,_} when (is_binary(Names) orelse Names==[]) -> []; + {surnames,_} when (is_binary(Surnames) orelse Surnames==[]) -> []; + {nick,_} when (is_binary(Nick) orelse Nick==[]) -> []; + {reader,_} when (is_list(Reader) orelse is_integer(Reader) orelse Reader==[]) -> []; + {unread,_} when (is_integer(Unread) orelse Unread==[]) -> []; + {last_msg,_} when (is_record(Last_msg,'Message') orelse Last_msg==[]) -> []; + {update,_} when (is_integer(Update) orelse Update==[]) -> []; + {created,_} when (is_integer(Created) orelse Created==[]) -> []; + {settings,_} when is_list(Settings) -> []; + {services,_} when is_list(Services) -> []; + {presence,_} when (is_binary(Presence) orelse Presence=='offline' orelse Presence=='online' orelse Presence==[]) -> []; + {status,_} when (Status=='deleted' orelse Status=='banned' orelse Status=='ban' orelse Status=='last_msg' orelse Status=='friend' orelse Status=='internal' orelse Status=='ignore' orelse Status=='authorization' orelse Status=='request' orelse Status==[]) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'Contact'), tl(tuple_to_list(D)))]), + CondFuns = [?COND_FUN(is_record(Rec, 'Feature')),?COND_FUN(is_record(Rec, 'Service'))], + Fields = [Settings,Services], + FieldNames = [settings,services], case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'ExtendedStar'{star = Star, from = From}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {star,_} when (Star==[] orelse is_record(Star,'Star')) -> []; + {from,_} when (From==[] orelse is_record(From,'Room') orelse is_record(From,'Contact')) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'ExtendedStar'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'Auth'{client_id = Client_id, dev_key = Dev_key, user_id = User_id, phone = Phone, token = Token, type = Type, sms_code = Sms_code, attempts = Attempts, services = Services, settings = Settings, push = Push, os = Os, created = Created, last_online = Last_online}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {client_id,_} when (is_binary(Client_id) orelse Client_id==[]) -> []; + {dev_key,_} when (is_binary(Dev_key) orelse Dev_key==[]) -> []; + {user_id,_} when (is_binary(User_id) orelse User_id==[]) -> []; + {phone,_} when (is_tuple(Phone) orelse is_binary(Phone) orelse Phone==[]) -> []; + {token,_} when (is_binary(Token) orelse Token==[]) -> []; + {type,_} when (is_atom(Type) orelse Type==[]) -> []; + {sms_code,_} when (is_binary(Sms_code) orelse Sms_code==[]) -> []; + {attempts,_} when (is_integer(Attempts) orelse Attempts==[]) -> []; + {services,_} when is_list(Services) -> []; + {settings,_} when (is_list(Settings) orelse Settings==[]) -> []; + {push,_} when (is_binary(Push) orelse Push==[]) -> []; + {os,_} when (Os=='web' orelse Os=='android' orelse Os=='ios' orelse Os==[]) -> []; + {created,_} when (is_integer(Created) orelse Created==[]) -> []; + {last_online,_} when (is_integer(Last_online) orelse Last_online==[]) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'Auth'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], + case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'Roster'{id = Id, names = Names, surnames = Surnames, email = Email, nick = Nick, userlist = Userlist, roomlist = Roomlist, favorite = Favorite, tags = Tags, phone = Phone, avatar = Avatar, update = Update, status = Status}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {id,_} when (is_integer(Id) orelse is_binary(Id) orelse Id==[]) -> []; + {names,_} when (is_binary(Names) orelse Names==[]) -> []; + {surnames,_} when (is_binary(Surnames) orelse Surnames==[]) -> []; + {email,_} when (is_binary(Email) orelse Email==[]) -> []; + {nick,_} when (is_binary(Nick) orelse Nick==[]) -> []; + {userlist,_} when is_list(Userlist) -> []; + {roomlist,_} when is_list(Roomlist) -> []; + {favorite,_} when is_list(Favorite) -> []; + {tags,_} when is_list(Tags) -> []; + {phone,_} when (is_binary(Phone) orelse Phone==[]) -> []; + {avatar,_} when (is_binary(Avatar) orelse Avatar==[]) -> []; + {update,_} when (is_integer(Update) orelse Update==[]) -> []; + {status,_} when (Status=='last_msg' orelse Status=='patch' orelse Status=='list' orelse Status=='update' orelse Status=='add' orelse Status=='nick' orelse Status=='remove' orelse Status=='del' orelse Status=='create' orelse Status=='get' orelse Status==[]) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'Roster'), tl(tuple_to_list(D)))]), + CondFuns = [?COND_FUN(is_record(Rec, 'Room')),?COND_FUN(is_record(Rec, 'Star')),?COND_FUN(is_record(Rec, 'Tag'))], + Fields = [Roomlist,Favorite,Tags], + FieldNames = [roomlist,favorite,tags], case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'Profile'{phone = Phone, services = Services, rosters = Rosters, settings = Settings, update = Update, balance = Balance, presence = Presence, status = Status}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {phone,_} when (is_binary(Phone) orelse Phone==[]) -> []; + {services,_} when (is_list(Services) orelse Services==[]) -> []; + {rosters,_} when (is_list(Rosters) orelse Rosters==[]) -> []; + {settings,_} when (is_list(Settings) orelse Settings==[]) -> []; + {update,_} when is_integer(Update) -> []; + {balance,_} when is_integer(Balance) -> []; + {presence,_} when (is_binary(Presence) orelse Presence=='online' orelse Presence=='offline' orelse Presence==[]) -> []; + {status,_} when (Status=='create' orelse Status=='delete' orelse Status=='update' orelse Status=='patch' orelse Status=='get' orelse Status=='remove' orelse Status==[]) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'Profile'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], + case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'Presence'{uid = Uid, status = Status}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {uid,_} when is_binary(Uid) -> []; + {status,_} when (is_binary(Status) orelse Status=='online' orelse Status=='offline' orelse Status==[]) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'Presence'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], + case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'Friend'{phone_id = Phone_id, friend_id = Friend_id, settings = Settings, status = Status}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {phone_id,_} when is_binary(Phone_id) -> []; + {friend_id,_} when is_binary(Friend_id) -> []; + {settings,_} when (is_list(Settings) orelse Settings==[]) -> []; + {status,_} when (Status=='ignore' orelse Status=='update' orelse Status=='confirm' orelse Status=='request' orelse Status=='unban' orelse Status=='ban') -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'Friend'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], + case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'act'{name = Name, data = Data}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {name,_} when (is_binary(Name) orelse Name==[]) -> []; + {data,_} when (is_list(Data) orelse is_integer(Data) orelse is_binary(Data)) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'act'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], + case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'Job'{id = Id, container = Container, feed_id = Feed_id, prev = Prev, next = Next, context = Context, proc = Proc, time = Time, data = Data, events = Events, settings = Settings, status = Status}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {id,_} when (is_integer(Id) orelse Id==[]) -> []; + {container,_} when (Container==[] orelse Container=='chain') -> []; + {feed_id,_} when is_record(Feed_id,'Job') -> []; + {prev,_} when (is_integer(Prev) orelse Prev==[]) -> []; + {next,_} when (is_integer(Next) orelse Next==[]) -> []; + {context,_} when (is_binary(Context) orelse is_integer(Context) orelse Context==[]) -> []; + {proc,_} when (is_record(Proc,'process') orelse is_integer(Proc) orelse Proc==[]) -> []; + {time,_} when (is_integer(Time) orelse Time==[]) -> []; + {data,_} when (is_list(Data) orelse is_binary(Data) orelse Data==[]) -> []; + {events,_} when (is_list(Events) orelse Events==[]) -> []; + {settings,_} when (is_list(Settings) orelse Settings==[]) -> []; + {status,_} when (Status=='restart' orelse Status=='complete' orelse Status=='stop' orelse Status=='pending' orelse Status=='delete' orelse Status=='update' orelse Status=='init' orelse Status==[]) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'Job'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'History'{roster_id = Roster_id, feed = Feed, size = Size, entity_id = Entity_id, data = Data, status = Status}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {roster_id,_} when is_binary(Roster_id) -> []; + {feed,_} when (Feed==[] orelse is_record(Feed,'StickerPack') orelse is_record(Feed,'act') orelse is_record(Feed,'muc') orelse is_record(Feed,'p2p')) -> []; + {size,_} when (is_integer(Size) orelse Size==[]) -> []; + {entity_id,_} when (is_integer(Entity_id) orelse Entity_id==[]) -> []; + {data,_} when (is_list(Data) orelse Data==[]) -> []; + {status,_} when (Status=='draft' orelse Status=='text' orelse Status=='location' orelse Status=='contact' orelse Status=='audio' orelse Status=='link' orelse Status=='file' orelse Status=='video' orelse Status=='image' orelse Status=='delete' orelse Status=='double_get' orelse Status=='get_reply' orelse Status=='last_msg' orelse Status=='last_loaded' orelse Status=='update' orelse Status=='get' orelse Status=='updated') -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'History'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'Schedule'{id = Id, proc = Proc, data = Data, state = State}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {id,_} when (is_tuple(Id) orelse is_integer(Id) orelse Id==[]) -> []; + {proc,_} when (is_binary(Proc) orelse is_integer(Proc) orelse Proc==[]) -> []; + {data,_} when (is_list(Data) orelse is_binary(Data)) -> []; + {state,_} when (State==[]) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'Schedule'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], + case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'Index'{id = Id, roster = Roster}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {id,_} when (Id==[]) -> []; + {roster,_} when is_list(Roster) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'Index'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], + case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'Whitelist'{phone = Phone, created = Created}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {phone,_} when (is_binary(Phone) orelse Phone==[]) -> []; + {created,_} when (is_integer(Created) orelse Created==[]) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'Whitelist'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], + case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'error'{code = Code}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {code,_} when (is_atom(Code) orelse Code==[]) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'error'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], + case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'ok'{code = Code}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {code,_} when (is_binary(Code) orelse Code==[]) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'ok'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], + case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'error2'{code = Code, src = Src}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {code,_} when (is_atom(Code) orelse Code==[]) -> []; + {src,_} when (is_integer(Src) orelse is_binary(Src) orelse Src==[]) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'error2'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], + case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'ok2'{code = Code, src = Src}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {code,_} when (is_atom(Code) orelse Code==[]) -> []; + {src,_} when (is_binary(Src) orelse is_tuple(Src) orelse Src==[]) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'ok2'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], + case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'MessageErr'{feed_id = Feed_id, msg_id = Msg_id, error = Error}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {feed_id,_} when (is_record(Feed_id,'p2p') orelse is_record(Feed_id,'muc')) -> []; + {msg_id,_} when (is_binary(Msg_id) orelse Msg_id==[]) -> []; + {error,_} when (is_record(Error,'error') orelse Error==[]) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'MessageErr'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'io'{code = Code, data = Data}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {code,_} when (is_record(Code,'MessageErr') orelse Code=='transcribe' orelse is_record(Code,'error2') orelse is_record(Code,'ok2') orelse is_record(Code,'error') orelse is_record(Code,'ok') orelse Code==[]) -> []; + {data,_} when (is_tuple(Data) orelse is_record(Data,'Roster') orelse is_binary(Data) orelse Data==[]) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'io'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'Ack'{table = Table, id = Id}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {table,_} when is_atom(Table) -> []; + {id,_} when (is_binary(Id) orelse Id==[]) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'Ack'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], + case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'MessageAck'{id = Id, next = Next, feed_id = Feed_id, created = Created, msg_id = Msg_id}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {id,_} when (is_integer(Id) orelse Id==[]) -> []; + {next,_} when (is_integer(Next) orelse Next==[]) -> []; + {feed_id,_} when (is_record(Feed_id,'p2p') orelse is_record(Feed_id,'muc')) -> []; + {created,_} when (is_integer(Created) orelse Created==[]) -> []; + {msg_id,_} when (is_binary(Msg_id) orelse Msg_id==[]) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'MessageAck'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'errors'{code = Code, data = Data}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {code,_} when (is_list(Code) orelse Code==[]) -> []; + {data,_} when (Data==[]) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'errors'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], + case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'PushService'{recipients = Recipients, id = Id, ttl = Ttl, module = Module, priority = Priority, payload = Payload}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {recipients,_} when is_list(Recipients) -> []; + {id,_} when (is_binary(Id) orelse Id==[]) -> []; + {ttl,_} when (is_integer(Ttl) orelse Ttl==[]) -> []; + {module,_} when (is_binary(Module) orelse Module==[]) -> []; + {priority,_} when (is_binary(Priority) orelse Priority==[]) -> []; + {payload,_} when (is_binary(Payload) orelse Payload==[]) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'PushService'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], + case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'PublishService'{message = Message, topic = Topic, qos = Qos}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {message,_} when (is_binary(Message) orelse Message==[]) -> []; + {topic,_} when (is_binary(Topic) orelse Topic==[]) -> []; + {qos,_} when (is_integer(Qos) orelse Qos==[]) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'PublishService'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], + case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'FakeNumbers'{phone = Phone, created = Created}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {phone,_} when (is_binary(Phone) orelse Phone==[]) -> []; + {created,_} when (is_integer(Created) orelse Created==[]) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'FakeNumbers'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], + case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'Draft'{data = Data, status = Status}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {data,_} when is_list(Data) -> []; + {status,_} when (Status=='delete' orelse Status=='get' orelse Status=='update') -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'Draft'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], + case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'handler'{name = Name, module = Module, group = Group}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {name,_} when is_atom(Name) -> []; + {module,_} when is_atom(Module) -> []; + {class,_} -> []; + {group,_} when is_atom(Group) -> []; + {config,_} -> []; + {state,_} -> []; + {seq,_} -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'handler'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], + case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'cx'{handlers = Handlers, actions = Actions, req = Req, module = Module, lang = Lang, path = Path, session = Session, formatter = Formatter, params = Params, node = Node, client_pid = Client_pid, state = State, from = From, vsn = Vsn}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {handlers,_} when is_list(Handlers) -> []; + {actions,_} when is_list(Actions) -> []; + {req,_} when (Req==[]) -> []; + {module,_} when (is_atom(Module) orelse Module==[]) -> []; + {lang,_} when (is_atom(Lang) orelse Lang==[]) -> []; + {path,_} when (is_binary(Path) orelse Path==[]) -> []; + {session,_} when (is_binary(Session) orelse Session==[]) -> []; + {formatter,_} when (Formatter=='json' orelse Formatter=='bert') -> []; + {params,_} when (is_list(Params) orelse Params==[]) -> []; + {node,_} when (is_atom(Node) orelse Node==[]) -> []; + {client_pid,_} when (Client_pid==[]) -> []; + {state,_} when (State==[]) -> []; + {from,_} when (is_binary(From) orelse From==[]) -> []; + {vsn,_} when (is_binary(Vsn) orelse Vsn==[]) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'cx'), tl(tuple_to_list(D)))]), + CondFuns = [?COND_FUN(is_record(Rec, 'handler'))], + Fields = [Handlers], + FieldNames = [handlers], case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'mqtt_topic'{topic = Topic, flags = Flags}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {topic,_} when is_binary(Topic) -> []; + {flags,_} when is_list(Flags) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'mqtt_topic'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'mqtt_session'{client_id = Client_id}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {client_id,_} when is_binary(Client_id) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'mqtt_session'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], + case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end; +validate(D = #'mqtt_route'{topic = Topic}, Acc) -> + ErrFields = lists:flatten( + [case {Field, F} of + {topic,_} when is_binary(Topic) -> []; + _ -> Field + end || {Field, F} <- lists:zip(record_info(fields, 'mqtt_route'), tl(tuple_to_list(D)))]), + CondFuns = [], + Fields = [], + FieldNames = [], + case validate(lists:zip3(CondFuns, FieldNames, Fields), [{ErrFields, D}|Acc]) of + ok -> validate(lists:zip(FieldNames,Fields), [{ErrFields, D}|Acc]); + Err -> Err + end. diff --git a/apps/roster/src/test/friend_test.erl b/apps/roster/src/test/friend_test.erl deleted file mode 100644 index 232bf39b56f1ab20e4f06b355acdd570ef998ea4..0000000000000000000000000000000000000000 --- a/apps/roster/src/test/friend_test.erl +++ /dev/null @@ -1,17 +0,0 @@ --module(friend_test). --include("roster.hrl"). --include("roster_test.hrl"). - --export([make_friends/2]). - -send_friend_request(User1PhoneId, User2PhoneId) -> - ok. - -confirm_friend_request(User1PhoneId, User2PhoneId) -> - ok. - -make_friends(User1PhoneId, User2PhoneId) -> -%% Steps -%% 1 - User1 - send friend request -%% 2 - User2 - confirm friend request - ok. \ No newline at end of file diff --git a/apps/roster/src/test/auth_test.erl b/apps/roster/test/auth_test.erl similarity index 100% rename from apps/roster/src/test/auth_test.erl rename to apps/roster/test/auth_test.erl diff --git a/apps/roster/src/test/call_room_test.erl b/apps/roster/test/call_room_test.erl similarity index 100% rename from apps/roster/src/test/call_room_test.erl rename to apps/roster/test/call_room_test.erl diff --git a/apps/roster/src/test/channel_test.erl b/apps/roster/test/channel_test.erl similarity index 100% rename from apps/roster/src/test/channel_test.erl rename to apps/roster/test/channel_test.erl diff --git a/apps/roster/src/test/job_tests.erl b/apps/roster/test/job_tests.erl similarity index 92% rename from apps/roster/src/test/job_tests.erl rename to apps/roster/test/job_tests.erl index a05e0f44113fbf87fe5c4b9ba62eb0f9054a467e..75cefea7569f9c72e01118fee2839a850d76b8ce 100644 --- a/apps/roster/src/test/job_tests.erl +++ b/apps/roster/test/job_tests.erl @@ -1,5 +1,6 @@ -module(job_tests). -compile(export_all). +-include_lib("kernel/include/logger.hrl"). -include_lib("n2o/include/n2o.hrl"). -include_lib("kvs/include/metainfo.hrl"). -include_lib("emqttc/include/emqttc_packet.hrl"). @@ -16,7 +17,7 @@ delete() -> [kvs:put(#'Whitelist'{phone = Phone, created = roster:now_msec()}) || Phone <- Phones ++ [PhoneA]], [roster:purge_user(Phone) || Phone <- [PhoneA | Phones]], [{B, BClientId, _}, {C, _, _}, {A, AClientId, _}] = - [begin C = roster_client:gen_name_reg(Phone), + [begin roster_client:gen_name_reg(Phone), {ClientId, Token} = roster_client:reg_fake_user(Phone), roster_client:receive_drop(), %roster_client:stop_client(C), roster_client:start_cli_receive(ClientId, Token), @@ -60,7 +61,7 @@ delete() -> #'Job'{id = DeleteId, proc = Proc, status=delete} = roster_client:send_receive(AClientId, #'Job'{id=DeleteId,feed_id = {act, <<"publish">>, A},status = delete}), timer:sleep(2000), #'History'{} = roster_client:send_receive(AClientId, #'History'{roster_id = A, feed = {act, <<"publish">>, A}, size = [], status = get}), - #'Job'{id=Id, proc = Proc} = roster_client:send_receive(AClientId, #'Job'{time = roster:now_msec() + 70000, feed_id = {act, <<"publish">>, A}, data = [ + #'Job'{proc = Proc} = roster_client:send_receive(AClientId, #'Job'{time = roster:now_msec() + 70000, feed_id = {act, <<"publish">>, A}, data = [ #'Message'{feed_id = roster:feed_key(p2p, A, B), from = A, to = B, files = [#'Desc'{id = <<"3">>, payload = <<"TestA!">>}], status = []}] , status = init}), #'History'{data=[#'Job'{status=pending}]} = roster_client:send_receive(AClientId, #'History'{roster_id = A, feed = {act, <<"publish">>, A}, size = [], status = get}), @@ -73,10 +74,8 @@ suite() -> [delete()]. check() -> case lists:all(fun(X) -> X == ok end, suite()) of - true -> roster:info("ALL TESTS PASSED", []), true; - false -> roster:info("TEST ERRORS", []), false end. + true -> ?LOG_INFO("ALL TESTS PASSED", []), true; + false -> ?LOG_INFO("TEST ERRORS", []), false end. purge_fake_users() -> length([roster:purge_user(Phone) || #'Auth'{phone = Phone} <- kvs:all('Auth'), byte_size(Phone) < 12]). - - diff --git a/apps/roster/src/test/main_test.erl b/apps/roster/test/main_test.erl similarity index 97% rename from apps/roster/src/test/main_test.erl rename to apps/roster/test/main_test.erl index d9d3166d250a5575e20c047c927b60ecf87ad352..c3aac4b5f2042686d788913f7ebdc104ef9e2243 100644 --- a/apps/roster/src/test/main_test.erl +++ b/apps/roster/test/main_test.erl @@ -1,5 +1,6 @@ -module(main_test). -compile(export_all). +-include_lib("kernel/include/logger.hrl"). -include("roster.hrl"). -include("roster_test.hrl"). @@ -13,13 +14,13 @@ is_test_session(Features) -> remove_fake_users_data() -> L = length([begin - roster:info(?MODULE, "FakeUserPurge:~p", [Phone]), roster:purge_user(Phone) + ?LOG_INFO("FakeUserPurge:~p", [Phone]), roster:purge_user(Phone) end || #'Auth'{settings = Settings, phone = Phone} <- kvs:all('Auth'), is_test_session(Settings)]), - roster:info(?MODULE, "RemovedFakeUsersCount:~p", [L]). + ?LOG_INFO("RemovedFakeUsersCount:~p", [L]). muc_msgs() -> RoomName = Room = <<"test_room_777">>, - Phones = [{APhone = <<"73777">>, admin}, {<<"79773">>, admin}, {<<"173737">>, member}, {<<"273747">>, member}], + Phones = [{_APhone = <<"73777">>, admin}, {<<"79773">>, admin}, {<<"173737">>, member}, {<<"273747">>, member}], %% Room2 = get_room_id(APhone, RoomName), Counter = length(Phones), roster:purge_room(Room), @@ -76,7 +77,7 @@ muc_msgs() -> [#'Message'{id = BFid, type = [read]}, #'Room'{}] = roster_client:send_receive(DClientId, last_res), %% Edit with msg_id - #'Message'{files = F} = roster_client:send_receive(BClientId, Counter, #'Message'{id=BFid, msg_id = <>, feed_id=#muc{name = Room}, + #'Message'{files = _F} = roster_client:send_receive(BClientId, Counter, #'Message'{id=BFid, msg_id = <>, feed_id=#muc{name = Room}, from=BPhoneId, files = [#'Desc'{id= <<"7">>, payload = <<"Edited by B!">>}], status = edit}), #'Ack'{} = roster_client:send_receive(BClientId, #'Message'{id=BFid, msg_id = <>, feed_id=#muc{name = Room}, diff --git a/apps/roster/src/test/micro_test.erl b/apps/roster/test/micro_test.erl similarity index 89% rename from apps/roster/src/test/micro_test.erl rename to apps/roster/test/micro_test.erl index 793b62026c408a319670b095d58ef467cc70bb76..e0bc62a418bd9ea6d9d8a9eb362f32955207ba33 100644 --- a/apps/roster/src/test/micro_test.erl +++ b/apps/roster/test/micro_test.erl @@ -61,10 +61,10 @@ test_roster_mismatch_data() -> Roster = #'Roster'{id = Rid} = roster:new_roster(P_UUID), kvs:put(#'LinkRoster'{id = R_UUID, phone_id = roster:phone_id(P_UUID, Rid)}), kvs:put(Profile = #'Profile'{phone = P_UUID, rosters = [Rid]}), - {reply, {bert, {io, {error, mismatch_user_data}, P_UUID}}, _, _} = - roster_client:test_info(micro_profile, Profile#'Profile'{status = create, + {reply, {bert, {io, {error, mismatch_user_data}, P_UUID}}, _, _} = + roster_client:test_info(micro_profile, Profile#'Profile'{status = create, rosters = [Roster#'Roster'{status = add, - id = R_UUID, + id = R_UUID, phone = P_UUID}]}, #cx{params = <<"sys_test">>}), {error, not_found} = kvs:get('Profile', P_UUID), %% Check we don't have left overs of new Profile {error, not_found} = kvs:get('LinkProfile', P_UUID), %% Check we don't have left overs of Linkage of new Profile @@ -77,7 +77,7 @@ test_roster_account_already_exists() -> R_UUID = list_to_binary(uuid:to_string(uuid:uuid4())), Roster = #'Roster'{id = Rid} = roster:new_roster(P_UUID), kvs:put(#'LinkRoster'{id = R_UUID, phone_id = roster:phone_id(P_UUID, Rid)}), - kvs:put(Profile = #'Profile'{phone = P_UUID, rosters = [Rid]}), + kvs:put(#'Profile'{phone = P_UUID, rosters = [Rid]}), kvs:put(#'LinkProfile'{id = P_UUID, phone = P_UUID}), {reply, {bert, {io, {error, account_already_exist}, <<>>}}, _, _} = roster_client:test_info(micro_roster, Roster#'Roster'{status = add, id = R_UUID}, #cx{params = <<"sys_test">>}), @@ -92,22 +92,22 @@ test_profile_import_error() -> R_UUID = list_to_binary(uuid:to_string(uuid:uuid4())), Roster = #'Roster'{id = Rid} = roster:new_roster(Phone), kvs:put(#'Index'{id = {nick, Nick}, roster = [50000]}), %% Give nick to some Roster - kvs:put(Profile = #'Profile'{phone = Phone, + kvs:put(Profile = #'Profile'{phone = Phone, services = [], - rosters = [Rid], - settings = [#'Feature'{id = <<"0ed58da1-097e-4241-a3dc-f4d38221e068">>, - key = <<"PHONE">>, - value = Phone, + rosters = [Rid], + settings = [#'Feature'{id = <<"0ed58da1-097e-4241-a3dc-f4d38221e068">>, + key = <<"PHONE">>, + value = Phone, group = <<"PROFILE_DATA">>}]}), {reply, {bert, {io, {error, nick}, P_UUID}}, _, _} = - roster_client:test_info(micro_profile, New = Profile#'Profile'{status = create, + roster_client:test_info(micro_profile, New = Profile#'Profile'{status = create, services = [#'Service'{type = wallet, login = <<"some-data">>}], %% Add some random data to the new Profile - phone = P_UUID, - rosters = [Roster#'Roster'{status = add, + phone = P_UUID, + rosters = [Roster#'Roster'{status = add, nick = Nick, %% Use the same nick for the New Roster (This should cause an error of used nick) - id = R_UUID, + id = R_UUID, phone = P_UUID}]}, #cx{params = <<"sys_test">>}), - Profile /= New, %% New profile has different data (added services), which we should not have in the DB + true = (Profile /= New), %% New profile has different data (added services), which we should not have in the DB {ok, Profile} = kvs:get('Profile', Phone), %% Check that the Profile hasn't change in the DB ok. @@ -121,14 +121,14 @@ test_creation_of_account_with_taken_username() -> {reply, {bert, {io, {error, nick}, P_UUID}}, _, _} = roster_client:test_info(micro_profile, #'Profile'{status = create, phone = P_UUID, - rosters = [#'Roster'{status = add, + rosters = [#'Roster'{status = add, nick = Nick, %% Use the same nick for the New Roster (This should cause an error of used nick) - id = R_UUID, - phone = P_UUID}]}, #cx{params = <<"sys_test">>}), + id = R_UUID, + phone = P_UUID}]}, #cx{params = <<"sys_test">>}), case length(kvs:all('Roster')) =:= RosterAmount of false -> throw("Unwanted Roster!"); true -> ok - end, + end, ok. test_micro() -> @@ -147,7 +147,7 @@ test_micro() -> [#'LinkProfile'{phone = APhone, id = ALinkedPId}] = kvs:index('LinkProfile', phone, APhone), [#'LinkRoster'{phone_id = APhoneId, id = ALinkedAId}] = kvs:index('LinkRoster', phone_id, APhoneId), ALinkedAIdBin = micro:norm_uuid(ALinkedAId), - ALinkedPIdBin = micro:norm_uuid(ALinkedPId), + _ALinkedPIdBin = micro:norm_uuid(ALinkedPId), OldModule = application:get_env(roster, validate_token, ?MODULE), application:set_env(roster, validate_token, ?MODULE), {ok, ALinkedAIdBin} = validate_token(<>), @@ -301,7 +301,7 @@ test_account2() -> {ok, #'Profile'{rosters = [RosterId]}} = kvs:get('Profile', APhoneUuid), {ok, #'Roster'{}} = kvs:get('Roster', RosterId), APhoneId = roster:phone_id(APhoneUuid, RosterId), - [#'LinkRoster'{id = ARosterLnkId, phone_id = APhoneId}] + [#'LinkRoster'{phone_id = APhoneId}] = kvs:index('LinkRoster', phone_id, roster:phone_id(APhoneUuid, RosterId)), [roster_client:stop_client(ClientId) || ClientId <- [BridgeClient, AClientId]], ok. @@ -323,37 +323,40 @@ suite() -> check() -> roster:check(?MODULE). add_users(UIDs)-> - BridgeClient = ?SYS_BRIDGE_TEST_CLIENT, - roster_client:start_client(BridgeClient), - L=[begin - UUID = micro:norm_uuid(micro:to_uuid(phone, U)), - micro:purge_user(UUID), - kvs:put(#'Whitelist'{phone = UUID, created = roster:now_msec()}), - ClientId = roster_client:gen_name(UUID), - #'Profile'{status = create, rosters=[#'Roster'{id = RUUID, phone = UUID}]} = roster_client:send_receive(BridgeClient, - #'Profile'{phone = UUID, rosters = [#'Roster'{id = micro:norm_uuid(uuid:uuid4()), phone = UUID}], status = create}), - {ok, #'Profile'{rosters = [RId]}} = kvs:get('Profile', UUID), - {UUID, RId, ClientId,MStatus} - end || {U,MStatus} <- UIDs] + BridgeClient = ?SYS_BRIDGE_TEST_CLIENT, + roster_client:start_client(BridgeClient), + [begin + UUID = micro:norm_uuid(micro:to_uuid(phone, U)), + micro:purge_user(UUID), + kvs:put(#'Whitelist'{phone = UUID, created = roster:now_msec()}), + ClientId = roster_client:gen_name(UUID), + #'Profile'{status = create, rosters=[#'Roster'{phone = UUID}]} = + roster_client:send_receive(BridgeClient, + #'Profile'{phone = UUID, + rosters = [#'Roster'{id = micro:norm_uuid(uuid:uuid4()), phone = UUID}], + status = create}), + {ok, #'Profile'{rosters = [RId]}} = kvs:get('Profile', UUID), + {UUID, RId, ClientId,MStatus} + end || {U,MStatus} <- UIDs] . del_rosters() -> BridgeClient = ?SYS_BRIDGE_TEST_CLIENT, - Users=[{Auuid, AR, AClientId,AS}, {Buuid, BR, BClientId,BS}, {Cuuid, CR, CClientId,CS} | _ ]= - add_users(Phones=[APhone|_]=[{<<"773">>,admin},{<<"5333">>,admin},{ <<"377">>,user}]), + Users=[{Auuid, AR, AClientId, _AS}, {_Buuid, _BR, BClientId, _BS}, {_Cuuid, _CR, CClientId, _CS} | _ ] = + add_users(Phones = [{<<"773">>,admin},{<<"5333">>,admin},{ <<"377">>,user}]), TokenPattern = <<"valid,">>, - RoomNames = [<<"test_room_A">>, <<"test_room_B">>, <<"test_room_C">>, - <<"test_room_D">>, <<"test_room_E">>, <<"test_room_F">>], - Counter = length(Users), - [{A,MA},{B,MB},{C,MC}|_]= [begin application:set_env(roster, validate_token, ?MODULE), - [#'LinkRoster'{id = RosterLnkId, phone_id = PhoneId}] = kvs:index('LinkRoster', phone_id, PhoneId=roster:phone_id(UUID, R)), - {ok, AccId} = validate_token(<>), + %% RoomNames = [<<"test_room_A">>, <<"test_room_B">>, <<"test_room_C">>, + %% <<"test_room_D">>, <<"test_room_E">>, <<"test_room_F">>], + [{A, _MA}, {B, _MB}, {C, _MC} | _] = + [begin application:set_env(roster, validate_token, ?MODULE), + [#'LinkRoster'{id = RosterLnkId, phone_id = PhoneId}] = kvs:index('LinkRoster', phone_id, PhoneId=roster:phone_id(UUID, R)), + {ok, AccId} = validate_token(<>), #'Profile'{phone = UUID, rosters = [#'Roster'{id = RosterId, phone = UUID}]} - = start_cli_receive(ClientId, Token2 = <>), + = start_cli_receive(ClientId, <>), {roster:phone_id(UUID, RosterId),#'Member'{presence = online, phone_id = PhoneId, status = Status}} end || {UUID,R,ClientId,Status}<-Users], roster_client:set_filer([AClientId, BClientId, CClientId], filter_friend), - OldModule = application:get_env(roster, validate_token, ?MODULE), + application:get_env(roster, validate_token, ?MODULE), {ok, #'Profile'{phone = Auuid, rosters=[AR]}} = kvs:get('Profile', Auuid), % {ok, #'LinkProfile'{id = ABinPhoneUuid, phone = APhoneUuid}} = kvs:get('LinkProfile', ABinPhoneUuid), #'Roster'{id=Auuid2, status = add} = roster_client:send_receive(BridgeClient, @@ -371,11 +374,11 @@ del_rosters() -> timer:sleep(1000), #'Profile'{settings = Settings, status=update}= roster_client:send_receive(BridgeClient,3, #'Profile'{status =update, phone = Auuid, settings = Settings = [#'Feature'{id = <<"dummy_id">>, key = <<"dummy_key">>}]}), - JobDelay = application:get_env(roster, job_delay, 1), + %% JobDelay = application:get_env(roster, job_delay, 1), application:set_env(roster, job_delay, 1), #'History'{} = roster_client:send_receive(AClientId, 3,#'History'{roster_id = A, feed = {act, <<"publish">>, A}, size = [], status = get}), %[_, ARId]=roster:parts_phone_id(A), [_, BRId]=roster:parts_phone_id(B) - #'Job'{proc = Proc} = roster_client:send_receive(BClientId, #'Job'{time = roster:now_msec() + 70000, feed_id = {act, <<"publish">>, B}, data = [ + #'Job'{} = roster_client:send_receive(BClientId, #'Job'{time = roster:now_msec() + 70000, feed_id = {act, <<"publish">>, B}, data = [ #'Message'{feed_id = roster:feed_key(p2p, A, B), from = B, to = A, files = [#'Desc'{id = <<"3">>, payload = <<"TestA!">>}], status = []}] , status = init}), timer:sleep(1000), @@ -421,7 +424,7 @@ del_rosters() -> %% #'Job'{proc = Proc} = roster_client:send_receive(BClientId, #'Job'{id = UpId, time = roster:now_msec() + 70000, feed_id = {act, <<"publish">>, B}, settings = ["New"], data = [ %% #'Message'{feed_id = roster:feed_key(p2p, A, B), from = B, to = A, files = [#'Desc'{id = <<"7">>, payload = <<"Next 3 TestA!">>}], status = []}] %% , status = update}), - [#'LinkRoster'{id = Auuid1Link, phone_id = PhoneId}] = kvs:index('LinkRoster', phone_id, roster:phone_id(Auuid, AR)), + [#'LinkRoster'{id = Auuid1Link}] = kvs:index('LinkRoster', phone_id, roster:phone_id(Auuid, AR)), roster_client:receive_drop(), %% [roster_client:send_receive(AClientId, Counter+1, #'Room'{id = Room, status = create, type = group, %% create room with 4 members %% name = Room, admins = [Owner#'Member'{alias = <<"Rick">>} | TAdmins], members = Members}) @@ -441,4 +444,4 @@ del_rosters() -> validate_token(<<"valid,", UserId/binary>>) -> {ok, UserId}; validate_token(<<"expired,", UserId/binary>>) -> {expired, UserId}; -validate_token(_) -> error. \ No newline at end of file +validate_token(_) -> error. diff --git a/apps/roster/src/test/rest_test.erl b/apps/roster/test/rest_test.erl similarity index 95% rename from apps/roster/src/test/rest_test.erl rename to apps/roster/test/rest_test.erl index 5019b979ea7c76530ac0705fec43ebb04ffa2a42..70b75bb808d0459d9901eae3eaeda5cf2c8ddb98 100644 --- a/apps/roster/src/test/rest_test.erl +++ b/apps/roster/test/rest_test.erl @@ -1,5 +1,6 @@ -module(rest_test). -compile(export_all). +-include_lib("kernel/include/logger.hrl"). -include_lib("n2o/include/n2o.hrl"). -include_lib("kvs/include/metainfo.hrl"). -include("roster.hrl"). @@ -13,12 +14,12 @@ suite() -> ]. check() -> case lists:all(fun(X) -> X == ok end, suite()) of - true -> roster:info("ALL REST TESTS PASSED", []), true; - false -> roster:info("TEST ERRORS", []), false end. + true -> ?LOG_INFO("ALL REST TESTS PASSED", []), true; + false -> ?LOG_INFO("TEST ERRORS", []), false end. test_rest_message_from_sys_user() -> Message = <<"Message from system User">>, - + %% Create User1 FakeProfileUUID1 = list_to_binary(uuid:to_string(uuid:uuid4())), FakeRosterUUID1 = list_to_binary(uuid:to_string(uuid:uuid4())), @@ -26,7 +27,7 @@ test_rest_message_from_sys_user() -> kvs:put(#'LinkProfile'{id = FakeProfileUUID1, phone = FakeProfileUUID1}), kvs:put(#'LinkRoster'{id = FakeRosterUUID1, phone_id = roster:roster_id(FakeProfileUUID1)}), FakePhoneId1 = roster:phone_id(FakeProfileUUID1), - + %% Create User2 FakeProfileUUID2 = list_to_binary(uuid:to_string(uuid:uuid4())), FakeRosterUUID2 = list_to_binary(uuid:to_string(uuid:uuid4())), @@ -46,7 +47,7 @@ test_rest_message_from_sys_user() -> test_rest_message_from_sys_user2() -> Message = <<"Message from system User">>, - + %% Create User1 FakeProfileUUID1 = list_to_binary(uuid:to_string(uuid:uuid4())), FakeRosterUUID1 = list_to_binary(uuid:to_string(uuid:uuid4())), @@ -54,14 +55,14 @@ test_rest_message_from_sys_user2() -> kvs:put(#'LinkProfile'{id = FakeProfileUUID1, phone = FakeProfileUUID1}), kvs:put(#'LinkRoster'{id = FakeRosterUUID1, phone_id = roster:roster_id(FakeProfileUUID1)}), FakePhoneId1 = roster:phone_id(FakeProfileUUID1), - + %% Create User2 ProfileUUID = list_to_binary(uuid:to_string(uuid:uuid4())), AccountId = list_to_binary(uuid:to_string(uuid:uuid4())), - LinkProfile = #'Profile'{status = create, phone = ProfileUUID, + LinkProfile = #'Profile'{status = create, phone = ProfileUUID, rosters = [#'Roster'{id = AccountId, phone = ProfileUUID, names = <<"Peshou">>}]}, roster_client:start_client(?SYS_BRIDGE_TEST_CLIENT), - Res = roster_client:send_receive(?SYS_BRIDGE_TEST_CLIENT, LinkProfile), + roster_client:send_receive(?SYS_BRIDGE_TEST_CLIENT, LinkProfile), {ok, #'LinkRoster'{phone_id = FakePhoneId2}} = kvs:get('LinkRoster', AccountId), ok = rest_gw:add_as_friend(<<"sys">>, FakePhoneId1, FakePhoneId2), %% Send friend request, where FakePhoneId1 is set as a system user, and accept automatically @@ -74,7 +75,7 @@ test_rest_message_from_sys_user2() -> test_rest_message_from_regular_user() -> Message = <<"What' up my man!!">>, - + %% Create User1 FakeProfileUUID1 = list_to_binary(uuid:to_string(uuid:uuid4())), FakeRosterUUID1 = list_to_binary(uuid:to_string(uuid:uuid4())), @@ -82,7 +83,7 @@ test_rest_message_from_regular_user() -> kvs:put(#'LinkProfile'{id = FakeProfileUUID1, phone = FakeProfileUUID1}), kvs:put(#'LinkRoster'{id = FakeRosterUUID1, phone_id = roster:roster_id(FakeProfileUUID1)}), FakePhoneId1 = roster:phone_id(FakeProfileUUID1), - + %% Create User2 FakeProfileUUID2 = list_to_binary(uuid:to_string(uuid:uuid4())), FakeRosterUUID2 = list_to_binary(uuid:to_string(uuid:uuid4())), @@ -103,4 +104,4 @@ test_rest_message_from_regular_user() -> ok = case roster_db:get_chain('Message', roster:feed_key(#p2p{from = FakePhoneId1, to = FakePhoneId2})) of %% Message is successful [#'Message'{type = [sys]}, #'Message'{files = [#'Desc'{payload = Message}]} | _] -> ok; [] -> error - end. \ No newline at end of file + end. diff --git a/apps/roster/src/test/room_test.erl b/apps/roster/test/room_test.erl similarity index 95% rename from apps/roster/src/test/room_test.erl rename to apps/roster/test/room_test.erl index de839c777dee93adee0fccc57b20f8403563560f..4c008832eb608cdf53faf04ba312d0934d6edd83 100644 --- a/apps/roster/src/test/room_test.erl +++ b/apps/roster/test/room_test.erl @@ -7,6 +7,7 @@ check_alias_increment/0, add_many_members/0, link/0, + suite/0, test_joinlink/0 ]). -define(BLANK_LINK, <<"UNDEFINED">>). @@ -74,19 +75,19 @@ add_many_members() -> status = add, type = group, members = to_member(lists:nthtail(2, TestClients))}), %% check what all members aliases is equal to their first name - [ReceivedAlias == SentFName || {#'Member'{alias = ReceivedAlias}, #'Member'{names = SentFName}} <- lists:zip(Members, to_member(lists:nthtail(2, TestClients)))], + [true] = lists:usort([ReceivedAlias == SentFName || {#'Member'{alias = ReceivedAlias}, #'Member'{names = SentFName}} <- lists:zip(Members, to_member(lists:nthtail(2, TestClients)))]), [roster_client:stop_client(ClientId) || {_, ClientId, _} <- TestClients], ok. link() -> RoomName = Room = <<"test_room_link">>, - Phones = [{APhone = <<"1">>, admin}, {BPhone = <<"2">>, admin}, {<<"3">>, member}, {<<"4">>, member}], + Phones = [{APhone = <<"1">>, admin}, {_BPhone = <<"2">>, admin}, {<<"3">>, member}, {<<"4">>, member}], %% Room2 = get_room_id(APhone, RoomName), Counter = length(Phones), roster:purge_room(RoomName), Feed = #muc{name = Room}, PCs = [{_, AClientId, _, _} | [{BPhoneId, BClientId, _, _}, - {CPhoneId, _, CM, _}, {DPhoneId, _, _, _}] = _] = + {CPhoneId, _, CM, _}, {_DPhoneId, _, _, _}] = _] = [begin roster:purge_user(Phone), {ClientId, Token} = roster_client:reg_fake_user(Phone), roster_client:start_cli_receive(ClientId, Token), @@ -120,7 +121,7 @@ link() -> roster:purge_user(EPhone = <<"5">>), {EClientId, EToken} = roster_client:reg_fake_user(EPhone), roster_client:start_cli_receive(EClientId, EToken), - EM = #'Member'{presence = online, feed_id = Feed, phone_id = EPhoneId = roster:phone_id(EPhone), status = member}, + EM = #'Member'{presence = online, feed_id = Feed, phone_id = _EPhoneId = roster:phone_id(EPhone), status = member}, #'Room'{status = add, unread = U} = roster_client:send_receive(AClientId, Counter + 1, #'Room'{status = add, id = RoomID, admins = [EM]}), true = is_integer(U), @@ -145,7 +146,7 @@ link() -> #'History'{feed = #muc{name = RoomID}, roster_id = FPhoneId, entity_id = MesID, size = -30, status = get}), true = length(Msgs) == SIZE + 1, %% Send message from user F - #'Message'{id = MesID2} = roster_client:send_receive(FClientId, Counter + 2, #'Message'{feed_id = #muc{name = Room}, from = FPhoneId, to = Room, + #'Message'{id = _MesID2} = roster_client:send_receive(FClientId, Counter + 2, #'Message'{feed_id = #muc{name = Room}, from = FPhoneId, to = Room, files = [#'Desc'{id = <<"9as120">>, payload = <<"Some Text From F user">>}], status = []}), #'Room'{readers = [_ | _]} = roster_client:send_receive(FClientId, #'Room'{id = RoomID, status = get}), @@ -281,4 +282,4 @@ suite() -> test_joinlink() ]. -check() -> roster:check(?MODULE). \ No newline at end of file +check() -> roster:check(?MODULE). diff --git a/apps/roster/src/test/roster_test.erl b/apps/roster/test/roster_test.erl similarity index 96% rename from apps/roster/src/test/roster_test.erl rename to apps/roster/test/roster_test.erl index 46c6aec4aa63ac81d97d50f7e24f4aa333e26c95..fd20c90f817b5ec91e4be5ae07623e00406a8c19 100644 --- a/apps/roster/src/test/roster_test.erl +++ b/apps/roster/test/roster_test.erl @@ -1,5 +1,6 @@ -module(roster_test). -compile(export_all). +-include_lib("kernel/include/logger.hrl"). -include_lib("n2o/include/n2o.hrl"). -include_lib("kvs/include/metainfo.hrl"). -include_lib("emqttc/include/emqttc_packet.hrl"). @@ -100,14 +101,14 @@ test_friend_multi() -> roster_client:stop_client(AClientId2), roster_client:stop_client(AClientId), roster_client:stop_client(BClientId). test_friend_introduction_msg() -> - Phones = [APhone, _BPhone] = [<<"7741">>, <<"3341">>], + Phones = [<<"7741">>, <<"3341">>], Counter = length(Phones), [{From, AClientId}, {To, BClientId}] = [begin roster:purge_user(Phone), {ClientId, Token} = roster_client:reg_fake_user(Phone), roster_client:start_cli_receive(ClientId, Token), [PhoneId] = roster_client:rosters(ClientId, Phone), {PhoneId, ClientId} - end || Phone <- Phones], %% create and connect fake users + end || Phone <- Phones], %% create and connect fake users roster_client:set_filer([AClientId, BClientId], filter_friend), Setting = #'Feature'{id = list_to_binary(uuid:to_string(uuid:uuid4())), key = <<"CONTACT_INTRODUCTION">>, @@ -124,15 +125,14 @@ test_friend_introduction_msg() -> = roster_client:receive_last2(BClientId, Counter + 1, #'Friend'{phone_id = To, friend_id = From, status = confirm}, 2). test_ack_message() -> - Phones = [APhone, _BPhone] = [<<"7741">>, <<"3341">>], - Counter = length(Phones), + Phones = [<<"7741">>, <<"3341">>], [{From, AClientId}, {To, BClientId}] = [begin roster:purge_user(Phone), {ClientId, Token} = roster_client:reg_fake_user(Phone), roster_client:start_cli_receive(ClientId, Token), [PhoneId] = roster_client:rosters(ClientId, Phone), {PhoneId, ClientId} end || Phone <- Phones], %% create and connect fake users \ - + roster_client:set_filer([AClientId, BClientId], filter_friend), Setting = #'Feature'{id = list_to_binary(uuid:to_string(uuid:uuid4())), key = <<"CONTACT_INTRODUCTION">>, @@ -141,7 +141,7 @@ test_ack_message() -> roster_client:send(AClientId, #'Friend'{phone_id = From, friend_id = To, settings = [Setting], status = request}), roster_client:send(BClientId, #'Friend'{phone_id = To, friend_id = From, status = confirm}), roster_client:send_receive(AClientId, last_res), - + roster_client:send(AClientId, #'Message'{msg_id = <<"some_msg_id1">>, feed_id = roster:feed_key(p2p, From, To), from = From, @@ -183,7 +183,7 @@ test_roster() -> [kvs:put(#'Whitelist'{phone = Phone, created = roster:now_msec()}) || Phone <- Phones], Counter = length(Phones), [roster:purge_user(Phone) || Phone <- Phones], - [{APhoneId, AClientId, AToken}, {BPhoneId, BClientId, BToken}] = + [{APhoneId, AClientId, AToken}, {BPhoneId, BClientId, _BToken}] = [begin {ClientId, Token} = roster_client:reg_fake_user(Phone), roster_client:start_cli_receive(ClientId, Token), @@ -359,8 +359,12 @@ test_roster() -> #'Contact'{reader = _LastId}]}] }] = roster_client:send_receive(AClientId, last_res), - #'Message'{id = ImageId2} = roster_client:send_receive(BClientId, 2, #'Message'{feed_id = Feed, from = BPhoneId, to = APhoneId, - files = [#'Desc'{id = <<"71">>, mime = <<"image">>, payload = <<"Image mime test!">>}, #'Desc'{id = <<"72">>, mime = <<"thumb">>, payload = <<"Image thumb test!">>, parentid = <<"71">>}], status = []}), + #'Message'{id = _ImageId2} = + roster_client:send_receive(BClientId, 2, + #'Message'{feed_id = Feed, from = BPhoneId, to = APhoneId, + files = [#'Desc'{id = <<"71">>, mime = <<"image">>, payload = <<"Image mime test!">>}, + #'Desc'{id = <<"72">>, mime = <<"thumb">>, payload = <<"Image thumb test!">>, parentid = <<"71">>}], + status = []}), #'Message'{id = ImageId} = roster_client:send_receive(BClientId, 2, #'Message'{feed_id = Feed, from = BPhoneId, to = APhoneId, files = [#'Desc'{id = <<"711">>, mime = <<"image">>, payload = <<"Image mime test2!">>}, #'Desc'{id = <<"712">>, mime = <<"thumb">>, payload = <<"Image mime test2!">>, parentid = <<"711">>}], status = []}), @@ -464,7 +468,7 @@ test_roster() -> %% add short audio test message for subscribe SpeechUrl = <<"https://www.googleapis.com/storage/v1/b/cloud-samples-tests/o/speech%2fbrooklyn.flac?alt=media">>, - SpeechText = <<"how old is the Brooklyn Bridge">>, + %% SpeechText = <<"how old is the Brooklyn Bridge">>, %% SpeechUrl = <<"https://nynja-defaults.s3.us-west-2.amazonaws.com/3cf97e8e0f1fe6001c9c1f01e90e7f25e29cdcc4ac4ef1df9658ae341a87526c_record_1561477824464.m4a">>, %% SpeechText = <<"don't know why I don't know how to pay pay pay pay people from their how to send it to another wallet">>, @@ -507,9 +511,10 @@ test_roster() -> #io{code = #ok{code = transcribe}, data = LongAudioSubscribeId} = roster_client:send_receive(AClientId, 1, - UpdLongMsg = UpdMessage#'Message'{id = LongAudioSubscribeId, - files = [UpdDesc#'Desc'{data = lists:keystore(<<"TYPE">>, #'Feature'.key, UpdData, UpdFeature#'Feature'{value = <<"long">>})}]}, - 15000), + UpdMessage#'Message'{ + id = LongAudioSubscribeId, + files = [UpdDesc#'Desc'{data = lists:keystore(<<"TYPE">>, #'Feature'.key, UpdData, UpdFeature#'Feature'{value = <<"long">>})}]}, + 15000), % This test doesn't pass because of problem in google account (gs_bucket in sys_config) % #'Message'{status = update, @@ -608,8 +613,7 @@ test_reg(Phone) when ?HOST == ?LOC -> roster_client:start_cli_receive(ClientId, Token3), roster_client:send_receive(ClientId, #'Profile'{status = get}), {ok, #'Auth'{type = [], token = Token3}} = kvs:get('Auth', ClientId), - roster_client:stop_client(ClientId), roster_client:stop_client(RegClientId); -test_reg(Phone) -> roster:info("test_reg(~p) skipped", [Phone]), ok. + roster_client:stop_client(ClientId), roster_client:stop_client(RegClientId). test_reg() when ?HOST == ?LOC -> roster:purge_user(Phone = <<"7727">>), @@ -661,8 +665,7 @@ test_reg() when ?HOST == ?LOC -> #io{code = #ok{code = logout}} = roster_client:send_receive(ClientId, #'Auth'{client_id = ClientId, type = logout}), roster_client:stop_client(ClientId), timer:sleep(500), - {error, not_found} = kvs:get('Auth', ClientId), ok; -test_reg() -> roster:info("test_reg() skipped", []), ok. + {error, not_found} = kvs:get('Auth', ClientId), ok. test_reg_jwt() when ?HOST == ?LOC -> kvs:put(#'Whitelist'{phone = <<"22">>, created = roster:now_msec()}), @@ -682,8 +685,7 @@ test_reg_jwt() when ?HOST == ?LOC -> roster_client:start_cli_receive(ClientId, Token), roster_client:receive_drop(), 1 = length(kvs:index('Auth', phone, Phone)), - roster_client:stop_client(ClientId); -test_reg_jwt() -> roster:info("test_reg_jwt() skipped", []), ok. + roster_client:stop_client(ClientId). %% Remote test for Staging ad Prod test_p2p_staging()-> test_latency("35.198.110.223", [], 70). @@ -694,11 +696,11 @@ test_latency() -> test_latency(?HOST, [], 10). test_latency(Server, Opts, MsgNumber) -> Phones = [_PhoneA, _] = [<<"71077">>, <<"72077">>], Counter = length(Phones), - [{FromRoster = #'Roster'{id = FromId, userlist = [_|Fs]}, AClientId, _}, {ToRoster = #'Roster'{id = ToId}, BClientId, _}] = + [{#'Roster'{id = FromId, userlist = [_|Fs]}, AClientId, _}, {#'Roster'{id = ToId}, BClientId, _}] = [begin roster:purge_user(Phone), {ClientId, Token} = roster_client:reg_fake_user(Phone, <<"DevKey_", Phone/binary>>, 1000, [{host, Server}|Opts]), - #'Profile'{rosters = [Roster = #'Roster'{id = RosteId}]} = roster_client:start_cli_receive(ClientId, Token, 0, [{host, Server}|Opts]), + #'Profile'{rosters = [Roster = #'Roster'{}]} = roster_client:start_cli_receive(ClientId, Token, 0, [{host, Server}|Opts]), {Roster, ClientId, Token} end || Phone <- Phones], [From, To]= [roster:phone_id(Phone, Id)||{Phone, Id} <-lists:zip(Phones, [FromId, ToId])], @@ -710,26 +712,28 @@ test_latency(Server, Opts, MsgNumber) -> _ -> ok end, Feed = roster:feed_key(p2p, From, To), - {Mx, Mn, Average} = lists:foldl(fun(_, {Max, Min, Av}) -> - T = erlang:timestamp(), - roster_client:send_receive(AClientId, Counter, #'Message'{feed_id = Feed, from = From, to = To, - files = [#'Desc'{id = <<"7">>, payload = <<"TestA!">>}], status = []}), - roster:info(?MODULE, "timestamp:A->B: ~p ms", [TS = timer:now_diff(erlang:timestamp(), T) / 1000]), - T2 = erlang:timestamp(), - roster_client:send_receive(BClientId, Counter, #'Message'{feed_id = Feed, from = To, to = From, - files = [#'Desc'{id = <<"3">>, payload = <<"TestB!">>}], status = []}), - roster:info(?MODULE, "timestamp:B->A: ~p ms", [TS2 = timer:now_diff(erlang:timestamp(), T2) / 1000]), - {max(max(Max, TS), TS2), min(min(Min, TS), TS2), Av + TS + TS2} - end, {0, 5000, 0}, lists:seq(1, MsgNumber)), - roster:info(?MODULE, "time summary:~p messages sent, max time: ~p, min time: ~p, average time: ~p", - [MsgNumber * 2, Mx, Mn, Average / (MsgNumber * 2)]), + {Mx, Mn, Average} = + lists:foldl(fun(_, {Max, Min, Av}) -> + T = erlang:timestamp(), + roster_client:send_receive(AClientId, Counter, #'Message'{feed_id = Feed, from = From, to = To, + files = [#'Desc'{id = <<"7">>, payload = <<"TestA!">>}], status = []}), + TS = timer:now_diff(erlang:timestamp(), T), + ?LOG_INFO("timestamp:A->B: ~p ms", [ TS / 1000]), + T2 = erlang:timestamp(), + roster_client:send_receive(BClientId, Counter, #'Message'{feed_id = Feed, from = To, to = From, + files = [#'Desc'{id = <<"3">>, payload = <<"TestB!">>}], status = []}), + TS2 = timer:now_diff(erlang:timestamp(), T2), + ?LOG_INFO("timestamp:B->A: ~p ms", [ TS2 / 1000]), + {max(max(Max, TS), TS2), min(min(Min, TS), TS2), Av + TS + TS2} + end, {0, 5000, 0}, lists:seq(1, MsgNumber)), + ?LOG_INFO("time summary:~p messages sent, max time: ~p, min time: ~p, average time: ~p", + [MsgNumber * 2, Mx, Mn, Average / (MsgNumber * 2)]), roster_client:stop_client(AClientId), roster_client:stop_client(BClientId). test_update_contacts() -> PhoneA = <<"357">>, AClientId = roster_client:gen_name(PhoneA), Phones = [<<"53533">>, <<"53774">>], - length(Phones), [roster:purge_user(Phone) || Phone <- [PhoneA | Phones]], PCs = [{B, BClientId, _}, {C, CClientId, TC}, {From, AClientId, TA}] = [begin {ClientId, Token} = roster_client:reg_fake_user(Phone), @@ -795,7 +799,7 @@ test_multi_muc() -> Phones = [{APhone = <<"001">>, admin}, {<<"002">>, admin}, {CPhone = <<"003">>, member}, {<<"004">>, member}], [roster:purge_room(R) || R <- RoomNames], Counter = length(Phones), - PCs = [{APhoneId, AClientId, _, _} | [{_, _, _, _}, {CPhoneId, CClientId, _, _}, {_, _, _, _}]] = + PCs = [{APhoneId, AClientId, _, _} | [{_, _, _, _}, {_CPhoneId, CClientId, _, _}, {_, _, _, _}]] = [begin roster:purge_user(Phone), {ClientId, Token} = roster_client:reg_fake_user(Phone), roster_client:start_cli_receive(ClientId, Token), @@ -901,7 +905,7 @@ test_muc() -> Counter = 1 + length(emqttd_server:subscribers(roster:room_topic(Room))), [] = [C||C<-emqttd_server:subscribers(roster:room_topic(Room)), C == BClientId], - [ok] = [ok||#'Member'{id = BMId, status = removed}<-roster:members(#muc{name = Room})], + [ok] = [ok|| #'Member'{status = removed} <- roster:members(#muc{name = Room})], {BClientId2, BToken2} = roster_client:reg_fake_user(BPhone, <<"DevKey2_", BPhone/binary>>), %% reg other session for B user roster_client:start_cli_receive(BClientId2, BToken2), @@ -967,7 +971,7 @@ test_muc() -> end, Counter, lists:nthtail(2, PCs)), #'Room'{status = add, admins = - [#'Member'{status = admin, alias = AAlias, presence = online, reader = ALastMsgId}, + [#'Member'{status = admin, alias = AAlias, presence = online}, #'Member'{status = admin, presence = online}]} = roster_client:send_receive(AClientId, Counter, #'Room'{status = add, id = Room, admins = Admins, members = Members}), @@ -1126,7 +1130,7 @@ test_muc() -> #'Profile'{rosters = [#'Roster'{roomlist = [#'Room'{members = Mmbrs, admins = Admns}]}]} = roster_client:send_receive(ClientId, #'Profile'{status = get}), [#'Member'{}, #'Member'{}] = Admns++Mmbrs - end || {PhoneId, ClientId, _, Token} <- TPCs], + end || {_PhoneId, ClientId, _, Token} <- TPCs], %% [begin start_cli_receive(ClientId, Token), %% #'Profile'{rosters = [#'Roster'{roomlist = @@ -1221,7 +1225,7 @@ test_muc() -> #'History'{data = [#'Message'{id = LastreadId2 = LastRoomMsgId}]} = roster_client:send_receive(EClientId, #'History'{feed = #muc{name = Room}, roster_id = EPhoneId, entity_id = LastRoomMsgId, size = [], status = get}), - #'History'{data = [#'Message'{id = LastreadId2} | Msgs2] = History2} = roster_client:send_receive(EClientId, + #'History'{data = [#'Message'{id = LastreadId2} | _Msgs2] = History2} = roster_client:send_receive(EClientId, #'History'{feed = #muc{name = Room}, roster_id = EPhoneId, entity_id = 0, size = 30, status = get}), UserHistLimit = length(History2) - 1, @@ -1253,7 +1257,7 @@ test_muc() -> test_muc_msgs() -> RoomName = Room = <<"test_room_777">>, - Phones = [{APhone = <<"73717">>, admin}, {<<"73727">>, admin}, {<<"73737">>, member}, {<<"73747">>, member}], + Phones = [{<<"73717">>, admin}, {<<"73727">>, admin}, {<<"73737">>, member}, {<<"73747">>, member}], %% Room2 = get_room_id(APhone, RoomName), Counter = length(Phones), roster:purge_room(Room), @@ -1482,10 +1486,10 @@ test_muc_msgs() -> test_call_room() -> RoomName = Room = <<"test_room_call">>, - Phones = [{APhone = <<"ACall">>, admin}, {<<"BCall">>, admin}, {<<"CCall">>, member}, {<<"DCall">>, member}], + Phones = [{<<"ACall">>, admin}, {<<"BCall">>, admin}, {<<"CCall">>, member}, {<<"DCall">>, member}], Counter = length(Phones), roster:purge_room(Room), - PCs = [{APhoneId, AClientId, AAdmin} | [{BPhoneId, BClientId, _}, {CPhoneId, CClientId, CMember}, {DPhoneId, DClientId, DMember} | _] = TPCs] = + PCs = [{APhoneId, AClientId, AAdmin}, {BPhoneId, _BClientId, _}, {CPhoneId, _CClientId, CMember}, {DPhoneId, DClientId, DMember} | _] = [begin roster:purge_user(Phone), {ClientId, Token} = roster_client:reg_fake_user(Phone), @@ -1516,7 +1520,7 @@ test_call_room() -> #'Room'{status = create, type = group, id = Room, name = RoomName, admins = [AAdmin]}), #'Room'{status = join, id = Room, type = group, - members = [#'Member'{id = CId, status = member, phone_id = CPhoneId}], admins = []} + members = [#'Member'{status = member, phone_id = CPhoneId}], admins = []} = roster_client:send_receive(ClientId, 2, #'Room'{status = join, type = group, id = Room, name = RoomName, admins = [], members = [CMember]}), #'Room'{status = add, id = Room, type = group, @@ -1562,7 +1566,7 @@ test_call_room() -> #'Room'{type = group} = lists:keyfind(Room, #'Room'.id, Rooms), 2 = length(ets:lookup(mqtt_subscriber, roster:room_topic(Room))), - [roster_client:stop_client(ClientId) || {_, ClientId, _} <- PCs], + [roster_client:stop_client(CId) || {_, CId, _} <- PCs], roster_client:stop_client(ClientId). test_messages() -> @@ -1658,8 +1662,8 @@ suite() -> ]. check() -> case lists:all(fun(X) -> X == ok end, suite()) of - true -> roster:info("ALL TESTS PASSED", []), true; - false -> roster:info("TEST ERRORS", []), false end. + true -> ?LOG_INFO("ALL TESTS PASSED", []), true; + false -> ?LOG_INFO("TEST ERRORS", []), false end. limit('Message', _) -> 2000000. forbid('Message') -> 20. @@ -1687,10 +1691,10 @@ test_migrate() -> migresia:migrate(roster), Arity = record_info(size, 'MigresiaTest'), Arity = tuple_size(element(2, kvs:get('MigresiaTest', 1))), ok; - Res -> roster:info(?MODULE, "~p = migresia:check(roster)", [Res]), + Res -> ?LOG_INFO("~p = migresia:check(roster)", [Res]), {error, test_migration} end catch Err:Rea -> - n2o:error(?MODULE, "Catch:~p~n", [n2o:stack_trace(Err, Rea)]), + ?LOG_ERROR("Catch:~p~n", [n2o:stack_trace(Err, Rea)]), throw({error, migrate}) after catch kvs:delete('schema_migrations', 30171023145947), file:delete(TestFile2) end. @@ -1700,11 +1704,12 @@ bpe() -> [kvs:put(#'Whitelist'{phone = Phone, created = roster:now_msec()}) || Phone <- Phones ++ [PhoneA]], [ begin try roster:purge_user(Phone) catch _:_ -> skip end end || Phone <- [PhoneA | Phones]], [{B, BClientId, _}, {C, _, _}, {A, AClientId, _}] = - [begin C = roster_client:gen_name_reg(Phone), - {ClientId, Token} = roster_client:reg_fake_user(Phone), - roster_client:receive_drop(), %roster_client:stop_client(C), - roster_client:start_cli_receive(ClientId, Token), - [{PhoneId, _, _} | _] = roster:list_rosters(Phone), {PhoneId, ClientId, Token} + [begin + roster_client:gen_name_reg(Phone), + {ClientId, Token} = roster_client:reg_fake_user(Phone), + roster_client:receive_drop(), %roster_client:stop_client(C), + roster_client:start_cli_receive(ClientId, Token), + [{PhoneId, _, _} | _] = roster:list_rosters(Phone), {PhoneId, ClientId, Token} end || Phone <- Phones ++ [PhoneA]], roster_client:set_filer([AClientId, BClientId], filter_friend), [roster_client:send_receive(AClientId, 1, #'Friend'{phone_id = A, friend_id = To, status = request}) || To <- [B, C]], @@ -1777,16 +1782,16 @@ test_bubble() -> roster:purge_room(RoomName), Feed = #muc{name = RoomName}, Users = [{APhoneId, AClientId, _}, - {BPhoneId, BClientId, _}, - {CPhoneId, CClientId, _}, - {DPhoneId, DClientId, _}] = + {_BPhoneId, _BClientId, _}, + {_CPhoneId, _CClientId, _}, + {_DPhoneId, _DClientId, _}] = [begin roster:purge_user(Phone), {ClientId, Token} = roster_client:reg_fake_user(Phone), roster_client:start_cli_receive(ClientId, Token), [PhoneId] = roster_client:rosters(ClientId, Phone), {PhoneId, ClientId, #'Member'{presence = online, feed_id = Feed, phone_id = PhoneId, status = MemberStatus}} - end || {Phone, MemberStatus} <- Phones], %% create and connect fake users + end || {Phone, MemberStatus} <- Phones], %% create and connect fake users ClientIds = [ClientId || {_, ClientId, _} <- Users], roster_client:set_filer(ClientIds, filter), [Admin1, Admin2 | Members] = [Member || {_, _, Member} <- Users], @@ -1804,8 +1809,8 @@ test_bubble() -> name = RoomName, admins = [Admin1#'Member'{alias = <<"Jared_Owner">>}, Admin2#'Member'{alias = <<"Sara_Admin">>}], - members = Members}), %% create room with 4 members - [#'Message'{id = Entity_ID}] = kvs:index('Message', to, <<"test_room_bubble">>), + members = Members}), %% create room with 4 members + [#'Message'{}] = kvs:index('Message', to, <<"test_room_bubble">>), CallBubble1 = #'CallBubbleJSON'{feed_id = RoomName, call_id = <<"bubble_test_call_id">>, type = <<"conference">>, @@ -1884,7 +1889,8 @@ test_room() -> roster:purge_user(EPhone = <<"5">>), {EClientId, EToken} = roster_client:reg_fake_user(EPhone), roster_client:start_cli_receive(EClientId, EToken), - EM = #'Member'{presence = online, feed_id = Feed, phone_id = EPhoneId = roster:phone_id(EPhone), status = member}, + EPhoneId = roster:phone_id(EPhone), + EM = #'Member'{presence = online, feed_id = Feed, phone_id = EPhoneId, status = member}, #'Room'{status = add, unread = U} = roster_client:send_receive(AClientId, Counter + 1, #'Room'{status = add, id = RoomID, admins = [EM]}), true = is_integer(U), @@ -1926,7 +1932,7 @@ test_room() -> clear_room_msg_test() -> RoomName = Room = <<"Room_clear_msgs">>, - Phones = [{APhone = <<"33123">>, admin}, {<<"4141">>, admin}, {<<"412414">>, member}], + Phones = [{<<"33123">>, admin}, {<<"4141">>, admin}, {<<"412414">>, member}], %% Room2 = get_room_id(APhone, RoomName), Counter = length(Phones), roster:purge_room(RoomName), @@ -1976,7 +1982,7 @@ clear_room_msg_test() -> history_update_test() -> RoomName = Room = <<"Room_update_test">>, - Phones = [{APhone = <<"3345">>, admin}, {<<"1236">>, admin},{<<"6697">>, member}], + Phones = [{<<"3345">>, admin}, {<<"1236">>, admin},{<<"6697">>, member}], %% Room2 = get_room_id(APhone, RoomName), Counter = length(Phones), roster:purge_room(Room), @@ -2039,11 +2045,11 @@ history_update_test() -> clear_test_only_admin() -> RoomName = Room = <<"Room_clear_admin_msgs2">>, - Phones = [{APhone = <<"33123000">>, admin}, {<<"4141000">>, admin}], + Phones = [{<<"33123000">>, admin}, {<<"4141000">>, admin}], %% Room2 = get_room_id(APhone, RoomName), Counter = length(Phones), roster:purge_room(Room), - PCs = [{APhoneId, AClientId, _, _}, {BPhoneId, BClientId, _, _}] = + PCs = [{APhoneId, AClientId, _, _}, {_BPhoneId, BClientId, _, _}] = [begin roster:purge_user(Phone), {ClientId, Token} = roster_client:reg_fake_user(Phone), roster_client:start_cli_receive(ClientId, Token), @@ -2304,7 +2310,7 @@ transcribe_test() -> %user B send transcribe MM= #'Message'{id = MsgId, files = [#'Desc'{}, #'Desc'{}] = Descs, status = update} = roster_client:send_receive(BClientId, 2, #'Message'{id = MsgId, feed_id = P2P, from = AUserId, to = BUserId, files = [DescTranscribe#'Desc'{data = [ F1, F2#'Feature'{value = BUserId}]}], status = update}), - roster:info(?MODULE, "@@~p", [MM]), + ?LOG_INFO("@@~p", [MM]), [[PhoneId] = lists:flatten([[P || P <- binary:split((roster:get_data(?USERS_KEY, TData))#'Feature'.value, <<",">>, [global]), P == PhoneId] || #'Desc'{data = TData, mime = <<"transcribe">>} <- Descs, (roster:get_data(?USERS_KEY, TData))#'Feature'.value /= []]) || PhoneId <- [AUserId, BUserId]], @@ -2394,27 +2400,27 @@ pagination_contact_test(N, UserCount) -> end, [roster:purge_user(<>) || I <- lists:seq(1, UserCount)], - Users2 = [{ClientId, PhoneId, Token}|TUsers] = + Users2 = [{_ClientId, PhoneId, _Token} | TUsers] = [roster_data:reg_fake_user(<>)|| I <- lists:seq(1, UserCount)], Phone = roster:phone(PhoneId), - roster:info(?MODULE, "friendsip is started", []), + ?LOG_INFO("friendsip is started", []), [begin roster_client:test_info(roster_friend, #'Friend'{phone_id = PhoneId, friend_id = PId, status = request}, Phone), roster_client:test_info(roster_friend, #'Friend'{phone_id = PId, friend_id = PhoneId, status = confirm}, roster:phone(PId)) end|| {_C, PId, _} <- TUsers], - roster:info(?MODULE, "friends are added", []), + ?LOG_INFO("friends are added", []), #'Roster'{userlist = Users} = roster:roster(roster:roster_id(PhoneId)), UserCount = length(Users), - roster:info(?MODULE, "roster is got", []), + ?LOG_INFO("roster is got", []), {ok, #'Roster'{} = StoredRoster} = kvs:get('Roster', roster:roster_id(PhoneId)), - PageCounter = case UserCount div N of + PageCounter = case UserCount div N of Count when UserCount/N == Count -> Count; Count -> - Count + 1 + Count + 1 end, [LastContactsPage | TContactsPages] = TotalPages = roster:objlist(#'Roster'.userlist, StoredRoster, 0, N), - roster:info(?MODULE, "roster is splitted", []), + ?LOG_INFO("roster is splitted", []), Rem = UserCount rem N, Rem = length(LastContactsPage), PageCounter = length(TotalPages), @@ -2464,7 +2470,7 @@ pagination_test() -> = roster:objlist(#'Roster'.userlist, StoredRoster, LastMsgCreated, N), [[#'ExtendedStar'{from = #'Contact'{phone_id = PhoneId}}]] = roster:objlist(#'Roster'.favorite, StoredRoster, LastMsgCreated, N), - + MsgCreated = lists:nth(5, Creates), [[#'Contact'{}, _ ], [_, _, _]] = roster:objlist(#'Roster'.userlist, StoredRoster, MsgCreated - 1, N), @@ -2500,16 +2506,16 @@ pagination_test() -> #'Profile'{} = roster_client:start_cli_receive(ClientId, Token), ExStars = [#'ExtendedStar'{} | _] = roster_client:send_receive(ClientId, #'ExtendedStar'{}), UserCount = length(ExStars) - RoomCount + 1, - roster:info(?MODULE, "LastMsgCreated = ~p", [LastMsgCreated]), + ?LOG_INFO("LastMsgCreated = ~p", [LastMsgCreated]), #'Profile'{status = get, rosters = [#'Roster'{}]} = roster_client:send_receive(ClientId, #'Profile'{status = get, update = LastMsgCreated}), roster_client:send_receive(ClientId, unsort_last_res), % ??? LastMsgCreated changed to 0 ??? roster_profile.erl:90 - [#'Profile'{status = get} |_] + [#'Profile'{status = get} |_] = roster_client:send_receive(ClientId, 13, #'Profile'{status = get, update = 0, settings = [#'Feature'{key= <<"size">>, value = <<"3">>, group = <<"PAGINATION">>}]}), - %roster:info(?MODULE, "SplittedProfile = ~p", [SplittedProfile]), + %?LOG_INFO("SplittedProfile = ~p", [SplittedProfile]), SplittedProfile = [#'Profile'{status = get, rosters = [#'Roster'{}]} | _] = roster_client:send_receive(ClientId, unsort_last_res), 13 = length(SplittedProfile), @@ -2586,25 +2592,25 @@ test_expired(Host) -> Phone = <<"EXPIRETOKEN">>, {ClientId, Token} = roster_client:reg_fake_user(Phone, <<"DevKey_", Phone/binary>>, 1000, [{host, Host}]), ExpTimeout = case Host of ?LOC -> application:get_env(roster, auth_ttl, 60*15); _ -> 900 end, - Timeout = case Host of ?LOC -> application:set_env(roster, auth_ttl, 1), 1; _-> ExpTimeout end, + case Host of ?LOC -> application:set_env(roster, auth_ttl, 1); _-> ok end, {Time, _} = roster:depicle(Token), - roster:info(?MODULE, "first token: ~p", [{Time, Token}]), - roster:info(?MODULE, "timeout: ~p", [(Time - roster:now_msec())/1000]), + ?LOG_INFO("first token: ~p", [{Time, Token}]), + ?LOG_INFO("timeout: ~p", [(Time - roster:now_msec())/1000]), timer:sleep(Time - roster:now_msec()+1000), - roster:info(?MODULE, "after timeout", []), + ?LOG_INFO("after timeout", []), #'Auth'{type = update, token = Token2} = roster_client:start_cli_receive(ClientId, Token, 0, [{host, Host}]), {Time2, _} = roster:depicle(Token2), - roster:info(?MODULE, "update new token: ~p", [{Time2, Token2}]), + ?LOG_INFO("update new token: ~p", [{Time2, Token2}]), case catch roster_client:send_receive(ClientId, 1, #'Auth'{type = push}, 1000) of {error, _} -> ok; - R -> roster:info(?MODULE, "invalid result: ~p", [R]) + R -> ?LOG_INFO("invalid result: ~p", [R]) end, roster_client:stop_client(ClientId), case Host of ?LOC -> application:set_env(roster, auth_ttl, ExpTimeout); _-> ok end, timer:sleep(1000), roster_client:receive_drop(), #'Profile'{} = roster_client:start_cli_receive(ClientId, Token2, 0, [{host, Host}]), - roster:info(?MODULE, "connect with new token: ~p", [Time2]), + ?LOG_INFO("connect with new token: ~p", [Time2]), roster_client:stop_client(ClientId) end), ok. @@ -2612,7 +2618,7 @@ test_security(Host) -> Phones = [_APhone, _BPhone, _CPhone] = [<<"securetestA">>, <<"securetestB">>, <<"securetestC">>], [kvs:put(#'Whitelist'{phone = Phone, created = roster:now_msec()}) || Phone <- Phones], %% [roster:purge_user(Phone) || Phone <- Phones], - [{APhoneId, AClientId, #'Roster'{userlist = Fs}}, {BPhoneId, BClientId, BRoster},{_CPhoneId, CClientId, CRoster}] = + [{APhoneId, AClientId, #'Roster'{userlist = Fs}}, {BPhoneId, BClientId, _BRoster},{_CPhoneId, CClientId, _CRoster}] = [begin {ClientId, Token} = roster_client:reg_fake_user(Phone, <<"DevKey_", Phone/binary>>, 1000, [{host, Host}]), #'Profile'{rosters = [Roster = #'Roster'{id = RosterId}]} = roster_client:start_cli_receive(ClientId, Token, 0, [{host, Host}]), @@ -2656,7 +2662,7 @@ test_security(Host) -> case catch roster_client:send_receive(CClientId, 1, {callback, SendFun}, 1000) of {error, {timeout, _}} -> ok; #'Message'{} -> throw({error, unsecurity_publish}); - R -> roster:info(?MODULE, "unexpected result: ~p", [R]), + R -> ?LOG_INFO("unexpected result: ~p", [R]), throw({error, unexpected}) end, %% #'History'{data = [_, _]} %% only two message in history @@ -2679,7 +2685,7 @@ test_quick_disconnect(Host) -> [begin %timer:sleep(1), #'Profile'{} = roster_client:start_cli_receive(ClientId, Token, 0, [{host, Host}]), roster_client:stop_client(ClientId), - roster:info(?MODULE, "connect counter: ~p", [I]) + ?LOG_INFO("connect counter: ~p", [I]) end || I <- lists:seq(1, 10)], ok. diff --git a/apps/service/rebar.config b/apps/service/rebar.config deleted file mode 100644 index 3af62d09d982540eee7491d120627685fa701a14..0000000000000000000000000000000000000000 --- a/apps/service/rebar.config +++ /dev/null @@ -1 +0,0 @@ -{deps,[{bert, ".*", {git, "git://github.com/synrc/bert",[]}}]}. \ No newline at end of file diff --git a/apps/service/src/service.app.src b/apps/service/src/service.app.src index 817f38f8592724cb044f45a05c7ee77037f95169..127eb62416e0c91082f9c9909334386dc1f150f5 100644 --- a/apps/service/src/service.app.src +++ b/apps/service/src/service.app.src @@ -1,6 +1,6 @@ {application, service, [{description, "NYNJA Protocol 2.0"}, - {vsn, "1"}, + {vsn, "1.0.1"}, {registered, []}, {applications, [kernel,stdlib,roster,n2o,bert]}, {mod, { service, []}}, diff --git a/eqc/ebin/Emakefile b/eqc/ebin/Emakefile new file mode 100644 index 0000000000000000000000000000000000000000..410149bd665c167f854291c25822ccc446296134 --- /dev/null +++ b/eqc/ebin/Emakefile @@ -0,0 +1,2 @@ +{"../*", []}. +{"../../test/*", []}. diff --git a/eqc/ebin/shell b/eqc/ebin/shell new file mode 100644 index 0000000000000000000000000000000000000000..0baadcb075beda29b108c09f1fb2515da9fb61bf --- /dev/null +++ b/eqc/ebin/shell @@ -0,0 +1 @@ +erl -pz ../../_build/test/lib/*/ebin diff --git a/eqc/nynja_eqc.erl b/eqc/nynja_eqc.erl new file mode 100644 index 0000000000000000000000000000000000000000..627f3ecf31b1be68e04b2ff71c6b744b47194014 --- /dev/null +++ b/eqc/nynja_eqc.erl @@ -0,0 +1,828 @@ +%%% Created : 25 Feb 2020 by Thomas Arts +-module(nynja_eqc). + +-include_lib("eqc/include/eqc.hrl"). +-include_lib("eqc/include/eqc_statem.hrl"). +-include_lib("emqttc/include/emqttc_packet.hrl"). +-include_lib("roster/include/roster.hrl"). + +-compile([export_all, nowarn_export_all]). + +%%-define(HOST, os:getenv("NYNJA_HOST", "35.197.66.129")). %% staging +-define(HOST, os:getenv("NYNJA_HOST", "127.0.0.1")). +-define(FAKE_CODE, <<"903182">>). +-define(FAKE_PHONES, [<<"380500000000">>, <<"819036705059">>, <<"79999999999">>, <<"817012345678">>]). + +-define(BUG(Name, Action1, Action2), + case maps:get(Name, fixed_bugs(), false) of + true -> + Action1; + false -> + Action2 + end). + +fixed_bugs() -> + #{connected_is_online => false, + drop_connection_issue => false, + clear_issue => false}. + +verbose() -> + case whereis(?MODULE) of + undefined -> + register(?MODULE, self()); + _ -> + unregister(?MODULE) + end. + + +%% -- State and state functions ---------------------------------------------- +initial_state() -> + #{users => #{}, clientids => [], ws_conns => []}. + +%% -- Generators ------------------------------------------------------------- + +gen_client_id() -> + ?LET(Id, vector(12, choose($a, $z)), list_to_binary(Id)). + +%% -- Common pre-/post-conditions -------------------------------------------- +command_precondition_common(_S, _Cmd) -> + true. + +precondition_common(_S, _Call) -> + true. + +postcondition_common(_S, _Call, _Res) -> + true. + +%% -- Operations ------------------------------------------------------------- + + +uniqueClientId_args(_S) -> + [elements(?FAKE_PHONES)]. + +uniqueClientId(_Phone) -> + %% During test continuously increasing number + list_to_binary("c"++integer_to_list(os:system_time(microsecond) rem 1000000000)). + +uniqueClientId_next(#{clientids := Cs} = S, Value, [Phone]) -> + S#{clientids => Cs ++ [{Phone, Value}]}. + + +%% --- Operation: reg --- +login_pre(#{ws_conns := Conns, clientids := Cs}) -> + Conns /= [] andalso Cs /= []. + +login_args(#{ws_conns := Conns, clientids := Cs}) -> + [elements(Conns), elements(Cs), elements([web, ios, android, []])]. + +login_pre(#{ws_conns := Conns, clientids := Cs}, [Conn, C, _]) -> + lists:member(Conn, Conns) andalso lists:member(C, Cs). + +login(Connection, {Phone, ClientId}, OS) -> + [ #mqtt_packet{ variable = #mqtt_packet_connack{} } = M ] = + ws_send_and_ack(Connection, mqtt_connect(<<"reg_", ClientId/binary>>, <<>>)), + + Auth = nynja:auth_rec(ClientId, Phone), + try ws_send_and_ack(Connection, mqtt_publish(<<"reg_", ClientId/binary>>, Auth)) + catch + _:Reason -> + io:format("ERROR ~p: ~p\n", [ClientId, M]), + exit(Reason) + end, + + AuthVerify = auth_verify(ClientId, Phone, ?FAKE_CODE, OS), + case ws_send_and_ack(Connection, mqtt_publish(<<"reg_", ClientId/binary>>, AuthVerify)) of + [ #mqtt_packet{payload = #io{ code = {ok2, login, {_User, Token}}}} ] -> + ws_close(Connection, shutdown), %% DO not model brutal close here yet + Token; + Msgs -> + {error, Msgs} + end. + +login_next(#{clientids := Cs, ws_conns := Conns} = S, Value, [Conn, {Phone, SymClientId} = C, OS]) -> + NewS = add_client(S, Phone, #{client_id => SymClientId, + token => Value, + online => false, + os => OS}), + NewS#{clientids => Cs -- [C], + ws_conns => Conns -- [Conn]}. + +login_post(_S, _Args, Res) -> + is_binary(Res). + +%% --- Operation: connect --- +connect_pre(#{ws_conns := Conns} = S) -> + offline_users(S) /= [] andalso Conns /= []. + +connect_args(#{ws_conns := Conns} = S) -> + [elements(Conns), elements(offline_users(S))]. + +connect_pre(#{ws_conns := Conns} = S, [Conn, User]) -> + lists:member(Conn, Conns) andalso lists:member(User, offline_users(S)). + +connect_call(S, [Conn, {_, ClientId} = User]) -> + Token = user_value(token, User, S), + ws_send_and_ack(Conn, mqtt_connect(<<"emqttd_", ClientId/binary>>, Token)). + +connect_next(#{ws_conns := Conns} = S, _Value, [Conn, {Phone, ClientId}]) -> + Client = get_client(S, Phone, ClientId), + NewS = update_client(S, Phone, ClientId, Client#{ws_conn => Conn, + online => true}), + NewS#{ws_conns => Conns -- [Conn]}. + +connect_post(_S, [_, _User], Res) -> + case Res of + [ #mqtt_packet{ variable = #mqtt_packet_connack{}} ] -> + true; + _ -> + eq(Res, connack) + end. + + +%% --- Operation: logout --- +logout_pre(S) -> + online_users(S) /= []. + +logout_args(S) -> + [elements(online_users(S))]. + +logout_pre(S, [User]) -> + user_value(online, User, S). + +logout_call(S, [User]) -> + Connection = user_value(ws_conn, User, S), + ws_close(Connection, shutdown), + ?BUG(drop_connection_issue, ok, timer:sleep(100)). + +logout_next(S, _Value, [{Phone, ClientId}]) -> + Client = get_client(S, Phone, ClientId), + case maps:get(os, Client, []) of + OS when OS == mobile -> %% which it never is + update_client(S, Phone, ClientId, + maps:without([ws_conn], Client#{online => false})); + _ -> + remove_client(S, Phone, ClientId) + end. + + +%% --- Operation: clear sessions --- +clear_pre(S) -> + ?BUG(clear_issue, online_users(S) /= [], false). + +clear_args(S) -> + [elements(online_users(S))]. + +clear_pre(S, [User]) -> + lists:member(User, online_users(S)). + +clear_call(S, [{_, ClientId} = User]) -> + Connection =user_value(ws_conn, User, S), + AuthClear = auth_clear(), + [#mqtt_packet{payload = Pl}] = ws_send_and_ack(Connection, mqtt_publish(<<"emqttd_", ClientId/binary>>, AuthClear)), + #io{code = {ok, cleared}} = Pl. + +clear_next(#{users := Users} = S, _Value, [{Phone, ClientId}]) -> + %% in order to test whether I am remotely closed, model keeps connection open, + %% but marks online as false. + Clients = maps:get(clients, maps:get(Phone, Users, [])), + lists:foldl(fun({Id, Client}, NewS) -> + case Id =/= ClientId of + true -> + update_client(NewS, Phone, Id, Client#{online => false}); + false -> + NewS + end + end, S, maps:to_list(Clients)). + +clear_post(_S, [_], _Res) -> + %% check the database, all sessions, apart from active one should be gone + true. + +%% --- Operation: disconnected --- +disconnected_pre(S) -> + offline_connected_users(S) /= []. + +disconnected_args(S) -> + [elements(offline_connected_users(S))]. + +disconnected_pre(S, [User]) -> + lists:member(User, offline_connected_users(S)). + +disconnected_call(S, [{_Phone, ClientId} = User]) -> + Connection = user_value(ws_conn, User, S), + %% I need some kind of valid message + AuthClear = auth_clear(), + catch ws_send_and_ack(Connection, mqtt_publish(<<"emqttd_", ClientId/binary>>, AuthClear)). + +disconnected_next(S, _Value, [{Phone, ClientId}]) -> + Client = get_client(S, Phone, ClientId), + update_client(S, Phone, ClientId, maps:without([ws_conn], Client)). + + +%% we get timeout instead of disconnect. That's possibly an error in the server +%% the server disconnects the accounts that are "clear"-ed. By doing so, it also removes the +%% tables, such that publishing after that makes little sense. +%% +%% 2020-03-06T12:07:42::roster_presence::<<"819036705059">>:<<"emqttd_eqc1583496462429911000">>:DISCONNECT:LOGOUT +%% 2020-03-06T12:07:42::roster_presence::<<"819036705059">>:<<"emqttd_eqc1583496462429911000">>:"{'Profile',<<\"819036705059\">>,[],[1],[{'Feature',<<\"819036705059_1_DEFAULT_LANGUAGE\">>,<<\"DEFAULT_LANGUAGE\">>,<<\"English:en\">>,<<\"LANGUAGE_SETTING\">>}],1583496462463,0,online,[]}" +%% 13:07:42.493 [error] Client(emqttd_eqc1583496462429911000@10.211.55.2:50600): Cannot publish to events/1/4/api/anon/emqttd_eqc1583496462429911000/ for ACL Deny +%% +disconnected_post(_S, [_], Res) -> + eq(Res, {'EXIT', down}). + + + +%% --- Operation: ws_connect --- +ws_connect_args(_S) -> + []. + +ws_connect() -> + nynja:user_client_pid(nynja:ws_connect()). + +ws_connect_next(#{ws_conns := Conns} = S, Value, []) -> + S#{ws_conns => Conns ++ [Value]}. + +ws_connect_post(_S, [], Res) -> + is_pid(Res). + +%% --- Operation: roster_id --- +roster_id_pre(S) -> + online_users(S) /= []. + +roster_id_args(S) -> + [elements(online_users(S))]. + +roster_id_pre(S, [User]) -> + user_value(online, User, S) == true + andalso user_value(roster_id, User, S) == undefined. %% to speed up testing + +roster_id_call(S, [{Phone, ClientId} = User]) -> + Connection = user_value(ws_conn, User, S), + GetProfile = nynja:profile_get(Phone), + Msgs = ws_send_and_ack(Connection, mqtt_publish(<<"emqttd_", ClientId/binary>>, GetProfile)), + {Responses, _} = nynja:msgs_split_topic(<<"actions/1/api/">>, Msgs), + case Responses of + [#mqtt_packet{payload = #'Profile'{ rosters = Rs, presence = Presence }}] -> + ?BUG(connected_is_online, online = Presence, skip), + [MyRoster] = [ Id || #'Roster'{id = Id} <- Rs], + MyRoster; + _ -> + {error, Msgs} + end. + +roster_id_next(S, Value, [{Phone,_} = User]) -> + case user_value(roster_id, User, S) of + undefined -> + Users = maps:get(users, S), + PhoneMap = maps:get(Phone, Users), + S#{users => Users#{Phone => PhoneMap#{roster_id => Value}}}; + _ -> S + end. + +roster_id_post(S, [User], Res) -> + is_integer(Res) andalso + case user_value(roster_id, User, S) of + undefined -> true; + Known -> + eq(Res, Known) + end. + + +%% --- Operation: profile --- +unread_pre(S) -> + online_users(S) /= []. + +unread_args(S) -> + [elements(online_users(S))]. + +unread_pre(S, [User1]) -> + user_value(online, User1, S) == true. + +unread_call(S, [{Phone, ClientId} = User]) -> + Connection = user_value(ws_conn, User, S), + GetProfile = nynja:profile_get(Phone), + Msgs = ws_send_and_ack(Connection, mqtt_publish(<<"emqttd_", ClientId/binary>>, GetProfile)), + {Responses, _} = nynja:msgs_split_topic(<<"actions/1/api/">>, Msgs), + case Responses of + [#mqtt_packet{payload = #'Profile'{ rosters = Rs, presence = Presence }}] -> + [Contacts] = [ Contacts || #'Roster'{userlist = Contacts} <- Rs], + Contacts; + _ -> + {error, Msgs} + end. + + +unread_post(S, [User, _], Res) -> + case is_list(Res) of + true -> + eq([ Contact || #'Contact'{unread = Unread, reader = [Last, LastRead]} = Contact <- Res, + LastRead + Unread /= Last], []); + false -> + Res + end. + + +%% --- Operation: become_friends --- +%% One may be friends already, but after this command, this is for sure and i state. +become_friends_pre(S) -> + %% Friends need to be online to acknowledge friend request + %% model later a waiting friend request + length(online_users(S)) > 1. + +become_friends_args(S) -> + Online = online_users(S), + ?LET([User1, User2 |_ ], shuffle(Online), + [User1, User2]). + +become_friends_pre(S, [{Phone1, _} = User1, {Phone2, _} = User2]) -> + Phone1 =/= Phone2 andalso + user_value(roster_id, User1, S) /= undefined andalso + user_value(roster_id, User2, S) /= undefined andalso + user_value(online, User1, S) andalso + user_value(online, User2, S). + +become_friends_call(S, [{Phone1, ClientId1} = User1, {Phone2, ClientId2} = User2]) -> + RId1 = user_value(roster_id, User1, S), + RId2 = user_value(roster_id, User2, S), + Con1 = user_value(ws_conn, User1, S), + Con2 = user_value(ws_conn, User2, S), + Cli1 = <<"emqttd_", ClientId1/binary>>, + Cli2 = <<"emqttd_", ClientId2/binary>>, + PhoneId1 = iolist_to_binary([Phone1, "_", integer_to_list(RId1)]), + PhoneId2 = iolist_to_binary([Phone2, "_", integer_to_list(RId2)]), + + Msgs1 = ws_send_and_ack(Con2, mqtt_publish(Cli2, nynja:friend_req(PhoneId2, PhoneId1))), + {API, Ses} = nynja:msgs_split_topic(<<"actions/1/api/">>, Msgs1), + + case {API, Ses} of + {[], [#mqtt_packet{ payload = #'Contact'{status = request}}]} -> + %% The contact info tells us whether Phone1 is online. Here it always is + %% + %% Phone1 receives the contacts from Phone2 (on all its clients, assumingly) + %% This contact info can come several times and contain the not-yet-read message of Client2 + Msgs = ws_send_and_ack(Con1, mqtt_publish(Cli1, nynja:friend_req_accept(PhoneId1, PhoneId2))), + + {Responses, _SesMsgs} = nynja:msgs_split_topic(<<"actions/1/api/">>, Msgs), + %% io:format("friend req: ~p with ses msgs ~p\n", [Responses, SesMsgs]), + + case Responses of + [#mqtt_packet{ payload = #io{code = {ok, added}}}] -> ok; + _ -> {error, friend_req, Msgs} + end; + + {[#mqtt_packet{ payload = #io{ code = invalid_data}}], _} -> + %% Already friends, return to API immediately + ok; + _ -> + {error, already_friends, Msgs1} + + end. + +become_friends_next(S, _Value, [{Phone1, _}, {Phone2, _}]) -> + S#{friends => lists:usort([{Phone1, Phone2}, {Phone2, Phone1} | maps:get(friends, S, [])])}. + +become_friends_post(_S, [_User1, _User2], Res) -> + eq(Res, ok). + +become_friends_features(_S, [{Phone1, _}, {Phone2, _}], _Res) -> + [{friends, Phone1, Phone2}]. + +%% --- Operation: send_msg --- +send_msg_pre(S) -> + %% Need online user with a friend + Friends = maps:get(friends, S, []), + [ User || {Phone, _Client} = User <- online_users(S), + lists:keymember(Phone, 1, Friends) ] /= []. + + +%% Roster Id must be part of the phone structure, rather than client +send_msg_args(#{friends := Friends} = S) -> + OnlineWithFriend = [ U || {Phone, _} = U <- online_users(S), + lists:keymember(Phone, 1, Friends) ], + ?LET({Phone1, _} = User, elements(OnlineWithFriend), + ?LET(FriendPhone, elements([ P2 || {P1, P2} <- Friends, P1 == Phone1]), + [User, FriendPhone, list_to_binary("message_" ++ integer_to_list(os:system_time(millisecond)))])). + +send_msg_pre(#{friends := Friends} = S, [{Phone1, _} = User1, Phone2, _]) -> + lists:member({Phone1, Phone2}, Friends) andalso + %% rosters are known, otherwise not friends + user_value(roster_id, User1, S) /= undefined andalso + user_value(roster_id, {Phone2, none}, S) /= undefined andalso + user_value(online, User1, S). + +%% Possibly rosterId is the same for all clients and therefore rosterid should be on different level, +%% not part of the client +send_msg_call(S, [{Phone1, ClientId1} = User1, Phone2, Msg]) -> + Rid1 = user_value(roster_id, User1, S), + Rid2 = user_value(roster_id, {Phone2, none}, S), + Cli1 = <<"emqttd_", ClientId1/binary>>, + Con1 = user_value(ws_conn, User1, S), + PhoneId1 = iolist_to_binary([Phone1, "_", integer_to_list(Rid1)]), + PhoneId2 = iolist_to_binary([Phone2, "_", integer_to_list(Rid2)]), + + FeedId = #p2p{from = min(PhoneId1, PhoneId2), to = max(PhoneId1, PhoneId2)}, + Publish = nynja:send_msg(Cli1, Msg, PhoneId1, PhoneId2, FeedId), + Msgs1 = ws_send_and_ack(Con1, mqtt_publish(Cli1, Publish)), + {P2P, Ses} = nynja:msgs_split_topic(<<"p2p/">>, Msgs1), + MsgIds = [ MsgId || #mqtt_packet{payload = #'Message'{id = MsgId, from = From, to = To}} <- P2P, + From == PhoneId1, + To == PhoneId2 ], + + case {MsgIds, Ses} of + {Ids, _} when is_list(Ids), length(Ids) > 0 -> + [ io:format("Ses = ~p\n", [Ses]) || Ses =/= [] ], + {FeedId, lists:last(Ids)}; + _ -> + {error, Msgs1} + end. + +%% Returns message Id +send_msg_next(S, Value, [{Phone_1, _}, Phone2, Msg]) -> + S#{msgs => [#{to => Phone2, from => Phone_1, feed_id => symb_element(1, Value), + msg_id => symb_element(2, Value), msg => Msg} | maps:get(msgs, S, [])]}. + +send_msg_post(_S, [_User1, _Phone2, _], Res) -> + case Res of + {error, _} -> Res; + {_, Id} -> is_integer(Id) + end. + +send_msg_features(_S, [{Phone1, _}, Phone2, _], _Res) -> + [{msg, Phone1, Phone2}]. + + +%% --- Operation: rcv_msgs --- +rcv_msgs_pre(S) -> + online_users_with_msgs(S) /= []. + +rcv_msgs_args(S) -> + ?LET(User, elements(online_users_with_msgs(S)), + [User, elements(user_msgs(User, S)), elements([[], 1, -1, 2, -2]), elements([get])]). %% TODO double_get and update + +rcv_msgs_pre(S, [User, Msg, _, _]) -> + user_value(online, User, S) andalso + lists:member(User, online_users_with_msgs(S)) andalso + lists:member(Msg, user_msgs(User, S)). + +rcv_msgs_call(S, [{Phone, ClientId} = User, #{feed_id := FeedId, msg_id := MsgId}, Size, Status]) -> + Rid = user_value(roster_id, User, S), + Cli1 = <<"emqttd_", ClientId/binary>>, + Con1 = user_value(ws_conn, User, S), + PhoneId = iolist_to_binary([Phone, "_", integer_to_list(Rid)]), + + GetMsg = #'History'{roster_id = PhoneId, + feed = FeedId, + size = Size, + entity_id = MsgId, + data = [], + status = Status}, + + Msgs1 = ws_send_and_ack(Con1, mqtt_publish(Cli1, GetMsg)), + %% Depending on whether client is logged in when sent or received after login in + {API, Ses} = nynja:msgs_split_topic(<<"actions/1/api">>, Msgs1), + {P2P, _} = nynja:msgs_split_topic(<<"p2p/">>, Msgs1), + Extra = Ses -- P2P, + + %% I seem to get p2p messages sometimes as well as api msgs... confusing + %% possibly different from being online when messages arives and not being online?? + %% In fact, we can have both! + + case {API, P2P} of + {[], [#mqtt_packet{payload = #'Message'{id = Id, files = Files}}]} -> + [{Id, [ M || #'Desc'{payload = M} <- Files]}]; + {[#mqtt_packet{payload = #'History'{data = Messages}}], _} -> + %% We actually can have both! + lists:foldr(fun(#'Message'{id = Id, files = Files}, Acc) -> + [{Id, [ M || #'Desc'{payload = M} <- Files]} | Acc] + end, [], Messages); + _ -> + {error, Msgs1} + end. + + +rcv_msgs_next(#{msgs := Sent} = S, _Value, [{_, ClientId}, Msg, _, _]) -> + S#{msgs => (Sent -- [Msg]) ++ + [ Msg#{received => maps:get(received, Msg, []) ++ [ClientId] }]}. + +rcv_msgs_post(#{msgs := Sent}, [_User, #{to := Phone, feed_id := FeedId, msg_id := MsgId}, Size, Status], Res) -> + case is_list(Res) of + true -> + case proplists:get_value(MsgId, Res, undefined) of + undefined -> + {not_found, MsgId, Res}; + _ -> + %% We can have messages in the system from earlier tests!! + %% Note that we also get messages returned that we have sent. + %% We get all with the same feed ID + {From, Until} = + {msgs_upto(Sent, Phone, FeedId, MsgId), + msgs_upto(lists:reverse(Sent), Phone, FeedId, MsgId)}, + Expected = + case Status of + get when Size == [] -> From; + get when Size >= 0 -> split_at(Size, lists:reverse(From)); + get when Size < 0 -> split_at(-Size, lists:reverse(Until)) + end, + eqc_statem:conj([eqc_statem:tag(prefix, + lists:all(fun(E) -> + lists:member(E, Res) + end, Expected)), + eqc_statem:tag(size, + if Size == [] -> length(Res) >= length(Expected); + true -> length(Res) =< abs(Size) + end)]) + end; + false -> + Res + end. + +msgs_upto([], _, _, _) -> + []; +msgs_upto([#{feed_id := FeedId, msg_id := MsgId, msg := Msg} | _], Phone, FeedId, MsgId) -> + [{MsgId, [Msg]}]; +msgs_upto([#{feed_id := FeedId, msg_id := Id, msg := Msg} | Rest], Phone, FeedId, MsgId) -> + [{Id, [Msg]} | msgs_upto(Rest, Phone, FeedId, MsgId)]; +msgs_upto([_ | Rest], Phone, FeedId, MsgId) -> + msgs_upto(Rest, Phone, FeedId, MsgId). + +split_at(_, []) -> + []; +split_at(0, _) -> + []; +split_at(N, [H|T]) -> + [H|split_at(N-1, T)]. + + + + + + + +weight(S, ws_connect) -> + max(0, 1 - length(maps:get(ws_conn, S, []))); +weight(S, uniqueClientId) -> + max(0, 1 - length(maps:get(clients, S, []))); +weight(S, login) -> + max(0, 3 - length(maps:get(ws_conn, S, []))); +weight(S, roster_id) -> length(online_users(S)); +weight(_S, feedid) -> 10; +weight(S, send_msg) -> + length(maps:get(friends, S, [])) * 20; +weight(S, become_friends) -> + case maps:get(friends, S, []) of + [] -> 4 * length(online_users(S)); + Friends -> + max(4, length(Friends)) + end; +weight(_S, logout) -> 1; +weight(_S, _Cmd) -> 1. + + + +%% --- ... more operations + +%% -- State operations ------------------------------------------------------- + +add_client(S, Phone, #{client_id := Id} = ClientMap) -> + Users = maps:get(users, S, #{}), + PhoneMap = maps:get(Phone, Users, #{}), + Clients = maps:get(clients, PhoneMap, #{}), + NewClients = Clients#{Id => maps:without([client_id], ClientMap)}, + S#{users => Users#{Phone => PhoneMap#{clients => NewClients}}}. + +update_client(S, Phone, ClientId, Client) -> + Users = maps:get(users, S, #{}), + PhoneMap = maps:get(Phone, Users, #{}), + Clients = maps:get(clients, PhoneMap, #{}), + S#{users => Users#{Phone => PhoneMap#{clients => Clients#{ClientId => Client}}}}. + +remove_client(S, Phone, ClientId) -> + Users = maps:get(users, S, #{}), + PhoneMap = maps:get(Phone, Users, #{}), + Clients = maps:get(clients, PhoneMap, #{}), + S#{users => Users#{Phone => PhoneMap#{clients => maps:without([ClientId], Clients)}}}. + + +get_client(S, {Phone, ClientId}) -> + get_client(S, Phone, ClientId). + +get_client(S, Phone, ClientId) -> + Users = maps:get(users, S), + case maps:get(Phone, Users, undefined) of + undefined -> undefined; + PhoneMap -> + case maps:get(clients, PhoneMap, undefined) of + undefined -> undefined; + Clients -> + maps:get(ClientId, Clients, undefined) + end + end. + +users(S) -> + Users = maps:get(users, S, #{}), + maps:fold(fun(Phone, PhoneMap, Acc) -> + ClientIds = maps:keys(maps:get(clients, PhoneMap)), + Acc ++ [ {Phone, ClientId} || ClientId <- ClientIds ] + end, [], Users). + + +online_users(S) -> + Users = maps:get(users, S, #{}), + maps:fold(fun(Phone, PhoneMap, Acc) -> + Clients = maps:to_list(maps:get(clients, PhoneMap)), + Acc ++ [ {Phone, ClientId} || {ClientId, Client} <- Clients, maps:get(online, Client, false)] + end, [], Users). + +online_users_with_msgs(S) -> + Users = maps:get(users, S, #{}), + WithMessages = [ Phone || {Phone, _, _, _} <- maps:get(msgs, S, []) ], + maps:fold(fun(Phone, PhoneMap, Acc) -> + Clients = maps:to_list(maps:get(clients, PhoneMap)), + Acc ++ [ {Phone, ClientId} || {ClientId, Client} <- Clients, + maps:get(online, Client, false), + lists:member(Phone, WithMessages)] + end, [], Users). + + + +offline_users(S) -> + Users = maps:get(users, S, #{}), + maps:fold(fun(Phone, PhoneMap, Acc) -> + Clients = maps:to_list(maps:get(clients, PhoneMap)), + Acc ++ [ {Phone, ClientId} || {ClientId, Client} <- Clients, not maps:get(online, Client, false) ] + end, [], Users). + +offline_connected_users(S) -> + Users = maps:get(users, S, #{}), + maps:fold(fun(Phone, PhoneMap, Acc) -> + Clients = maps:to_list(maps:get(clients, PhoneMap)), + Acc ++ [ {Phone, ClientId} || {ClientId, Client} <- Clients, + maps:is_key(ws_conn, Client), + not maps:get(online, Client, false) ] + end, [], Users). + +clients_user(S, Phone) -> + Users = maps:get(users, S, #{}), + PhoneMap = maps:get(Phone, Users, #{}), + Clients = maps:get(clients, PhoneMap, #{}), + [ {Phone, ClientId} || ClientId <- maps:keys(Clients) ]. + +user_value(roster_id, {Phone, _ClientId}, S) -> + Users = maps:get(users, S, #{}), + PhoneMap = maps:get(Phone, Users, #{}), + maps:get(roster_id, PhoneMap, undefined); +user_value(Key, User, S) -> + case get_client(S, User) of + undefined -> undefined; + Client -> + maps:get(Key, Client, undefined) + end. + +user_msgs({Phone, _}, S) -> + [ Msg || {P, _, _, _} = Msg <- maps:get(msgs, S, []), + P == Phone ]. + + + + +%% -- Property --------------------------------------------------------------- +prop_nynja() -> + eqc:dont_print_counterexample( + ?FORALL(Cmds, more_commands(5, commands(?MODULE)), + begin + {H, S, Res} = run_commands(Cmds), + + timer:sleep(100), + [ ws_close(Conn, brutal) || Conn <- open_sockets(S) ], + timer:sleep(100), %% Needed to prevent re-using sockets to quickly + check_command_names(Cmds, + aggregate(call_features(H), + measure(length, commands_length(Cmds), + pretty_commands(?MODULE, Cmds, {H, S, Res}, + Res == ok)))) + end)). + +bugs() -> bugs(10). + +bugs(N) -> bugs(N, []). + +bugs(Time, Bugs) -> + more_bugs(eqc:testing_time(Time, prop_nynja()), 20, Bugs). + + +open_sockets(#{ws_conns := Ws, users := Users}) -> + Open = maps:fold(fun(_, PhoneMap, Acc) -> + Clients = maps:get(clients, PhoneMap), + Acc ++ maps:values(Clients) + end, [], Users), + Ws ++ [ Conn || #{ws_conn := Conn} <- Open ]. + +mqtt_connect(ClientId, Password) -> + #mqtt_message{qos = WillQos, + retain = WillRetain, + topic = WillTopic, + payload = WillMsg} = #mqtt_message{topic = <<"version/11">>, + payload = <<>>}, + %% if will msg and topic is omitted: + %% 020-02-28 14:13:37.047 [info] <0.1150.0>@emqttd_protocol:trace:329 Client(undefined@10.211.55.2:65077): RECV CONNECT(Q0, R0, D0, ClientId=reg_eglhrhgr37wk74lvznu, ProtoName=MQTT, ProtoVsn=4, CleanSess=true, KeepAlive=90, Username=api, Password=******) + %% 2020-02-28 14:13:37.047 [error] <0.1150.0>@emqttd_protocol:process:203 Client(reg_eglhrhgr37wk74lvznu@10.211.55.2:65077): Username 'api' login failed for invalid_version + + Connect = #mqtt_packet_connect{client_id = ClientId, + proto_ver = 4, + proto_name = <<"MQTT">>, + will_flag = true, + will_retain = WillRetain, + will_qos = WillQos, + clean_sess = true, + keep_alive = ?KEEPALIVE, + will_topic = WillTopic, + will_msg = WillMsg, + username = <<"api">>, + password = Password}, + ?CONNECT_PACKET(Connect). + +mqtt_publish(ClientId, Auth) -> + mqtt_publish(ClientId, Auth, ?QOS_0). + +mqtt_publish(ClientId, Auth, Qos) -> + Topic = <<"events/1/4/api/anon/", ClientId/binary, "/">>, + PacketId = undefined, + Payload = term_to_binary(Auth), + ?PUBLISH_PACKET(Qos, Topic, PacketId, Payload). + +ws_close(Pid, _) -> + Pid ! close. + +ws_send_and_ack(Pid, MqttPacket) when is_pid(Pid) -> + Verbose = (whereis(?MODULE) /= undefined), + [ io:format("Send: ~p\n", [pp(MqttPacket)]) || Verbose ], + Pid ! {send, MqttPacket}, + Msgs = nynja:ws_receive_(Pid, [], any, 300), + [ io:format("Received ~p\n", [[pp(Msg) || Msg <- Msgs]]) || Verbose ], + Msgs. + +pp(#mqtt_packet{payload = Payload} = Packet) when is_binary(Payload) -> + Term = + try binary_to_term(Payload) + catch _:_ -> + Payload + end, + Packet#mqtt_packet{payload = Term}; +pp(Other) -> + Other. + + +decode_frame(Frame) -> + case Frame of + {binary, Bin} -> + case emqttc_parser:parse(Bin, none) of + {ok, #mqtt_packet{variable = #mqtt_packet_connack{}}, <<>>} -> + connack; + {ok, #mqtt_packet{payload = Payload} = Mqtt, <<>>} -> + Mqtt#mqtt_packet{payload = binary_to_term(Payload)}; + Other -> + io:format("Decoded frame ~p\n", [Bin]), + Other + end + end. + +auth_verify(ClientId, Phone, Code, OS) -> + #'Auth'{client_id = [], + dev_key = ClientId, + user_id = [], + phone = Phone, + token = [], + type = verify, + sms_code = Code, + attempts = [], + services = [], + settings = [{'Feature',<<"554324f4-f876-4581-ad39-c232db59a798">>, + <<"DEFAULT_LANGUAGE">>,<<"English:en">>, + <<"LANGUAGE_SETTING">>}], + push = [], + os = OS, %% [] | ios | android | web, + created = [], + last_online = []}. + +auth_clear() -> + {'Auth', [], [], [], [], [], clear, [], [],[], [], [],[],[],[]}. + + + +wrap_call(S, {call, M, Cmd, Args}) -> + CmdCall = list_to_atom(lists:concat([Cmd, "_call"])), + case eqc_function_exported:function_exported(M, CmdCall, 2) of + true -> + try {ok, eqc_statem:apply(M, CmdCall, [S, Args])} + catch _:Reason -> + {error, Reason, []} + end; + false -> + try {ok, eqc_statem:apply(M, Cmd, Args)} + catch _:Reason -> + {error, Reason, []} + end + end. + +symb_element(N, Value) -> + {call, erlang, element, [N, Value]}. diff --git a/eqc/nynja_test.erl b/eqc/nynja_test.erl new file mode 100644 index 0000000000000000000000000000000000000000..60f2f29dcaa346f4cc223b5ea9dba3a8b1c5ac27 --- /dev/null +++ b/eqc/nynja_test.erl @@ -0,0 +1,151 @@ +%%% Created : 25 Feb 2020 by Thomas Arts +%%% +%%% Create a Nynja server and start it with "mad dep com rep" +%%% In the server console add debugging: +%%% lager:set_loglevel(lager_console_backend, debug). +%%% +%%% Start an erlang shell using "rebar3 shell" +%%% in that Erlang shell you can run a test, observing the behaviour in +%%% server console +%% +%% These tests requires an additonal debug statement in deps/mochiweb/src/mochiweb_websocket.erl +%% It is easy to add this in place and then restart the server with "mad dep com rep" +%% Different from rebar3 changes made to the dependencies are not overwritten by getting the +%% old version of the code (good for debugging, requires a make clean now and then). +%% +%% Changes to make: +%% deps/mochiweb/src/mochiweb_websocket.erl around line 60 in function request/5 +%% Payload -> +%% io:format("I received correct payload ~p (Body = ~p)\n", [Payload, Body]), +-module(nynja_test). + +-include_lib("eqc/include/eqc.hrl"). +-include_lib("emqttc/include/emqttc_packet.hrl"). + +-compile([export_all, nowarn_export_all]). + +%% Change the host to localhost or whereever you run you niynja server +-define(HOST, os:getenv("NYNJA_HOST", "10.211.55.7")). + + +%% This tests shows that the web services is retrying to send messages even though the +%% server has decided that this is a rubish request and should have completely closed +%% the connection +post_rubish_test() -> + {ok, _} = application:ensure_all_started(gun), + {ok, ConnPid, _} = start_connection(), + gun:post(ConnPid, "/mqtt/test", [], ""), + receive + X -> io:format("~p\n", [X]) + after 5000 -> + exit(timeout) + end. + +%% Here we subscribe by creating a new user registration, but the request fails +%% and the websocket is closed on client side +%% but the server keeps looping on +%% "I received correct payload [<<>>] (Body = #Fun)" +unfinished_subscribe_test() -> + {ok, _} = application:ensure_all_started(gun), + {ok, ConnPid, MRef} = start_connection(), + Data = mqtt_connect(<<"quviq">>, false), + send_connect(ConnPid, MRef, Data), + %% this results in GOT unexpected {gun_ws,<0.460.0>,#Ref<0.3957994452.902037505.247187>, + %% {binary,<<32,2,0,0>>}} and now a ws connection is open and waiting + %% starting a second one for same user, closes the Websocket connection, but + %% kills the first active connection in the server, but keeps running with this second... + + io:format("Look at the console for a few seconds!\n"), + timer:sleep(10000), + + {ok, ConnPid2, MRef2} = start_connection(), + send_connect(ConnPid2, MRef2, Data). + +finished_subscribe_test() -> + {ok, _} = application:ensure_all_started(gun), + {ok, ConnPid, MRef} = start_connection(), + Data = mqtt_connect(<<"quviq">>), + send_connect(ConnPid, MRef, Data). + + +mqtt_connect(ClientId) -> + mqtt_connect(ClientId, true). + +mqtt_connect(ClientId, WillFlag) -> + #mqtt_message{qos = WillQos, + retain = WillRetain, + topic = WillTopic, + payload = WillMsg} = #mqtt_message{topic = <<"version/11">>, + payload = <<"version/11">>}, + %% if topic is omitted: + %% 020-02-28 14:13:37.047 [info] <0.1150.0>@emqttd_protocol:trace:329 Client(undefined@10.211.55.2:65077): RECV CONNECT(Q0, R0, D0, ClientId=reg_eglhrhgr37wk74lvznu, ProtoName=MQTT, ProtoVsn=4, CleanSess=true, KeepAlive=90, Username=api, Password=******) + %% 2020-02-28 14:13:37.047 [error] <0.1150.0>@emqttd_protocol:process:203 Client(reg_eglhrhgr37wk74lvznu@10.211.55.2:65077): Username 'api' login failed for invalid_version + + Connect = #mqtt_packet_connect{client_id = ClientId, + proto_ver = 4, + proto_name = <<"MQTT">>, + will_flag = WillFlag, + will_retain = WillRetain, + will_qos = WillQos, + clean_sess = true, + keep_alive = ?KEEPALIVE, + will_topic = WillTopic, + will_msg = if WillFlag -> WillMsg; true -> undefined end, + username = <<"api">>, + password = <<"">>}, + io:format("Connect = ~p\n", [Connect]), + emqttc_serialiser:serialise(?CONNECT_PACKET(Connect)). + + %% SENT: CONNECT(Q0, R0, D0, ClientId=quviq, ProtoName=MQTT, ProtoVsn=4, CleanSess=true, KeepAlive=90, Username=api, Password=*****) + +start_connection() -> + {ok, _} = application:ensure_all_started(gun), + {ok, ConnPid} = gun:open(?HOST, 8083), + MRef = monitor(process, ConnPid), + {ok, http} = gun:await_up(ConnPid), + gun:ws_upgrade(ConnPid, "/mqtt", [], #{protocols => [{<<"mqtt">>, gun_ws_h}]}), + receive + {gun_upgrade, ConnPid, StreamRef, [<<"websocket">>], Headers} -> + io:format("successful upgrade ~p ~p\n", [StreamRef, Headers]), + {ok, ConnPid, MRef}; + {gun_response, ConnPid, _, _, Status, Headers} -> + exit({ws_upgrade_failed, Status, Headers}); + {gun_error, ConnPid, _StreamRef, Reason} -> + exit({ws_upgrade_failed, Reason}) + after 5000 -> + exit(timeout) + end. + +send_connect(ConnPid, MRef, Data) -> + StreamRef = gun:ws_send(ConnPid, {binary, Data}), + %% This results in a hybi frame that is parsed correctly + receive + {gun_ws, ConnPid, StreamRef, Frame} -> + io:formay("Got ws ~p\n", [Frame]); + {gun_response, ConnPid, StreamRef, fin, _Status, _Headers} -> + no_data; + {gun_response, ConnPid, StreamRef, nofin, _Status, _Headers} -> + receive_data(ConnPid, MRef, StreamRef); + {'DOWN', MRef, process, ConnPid, Reason} -> + error_logger:error_msg("Oops!"), + exit(Reason); + X -> io:format("GOT unexpected ~p\n", [X]) + after 5000 -> + exit(timeout) + end. + + + +receive_data(ConnPid, MRef, StreamRef) -> + receive + {gun_data, ConnPid, StreamRef, nofin, Data} -> + io:format("nofin ~s~n", [Data]), + receive_data(ConnPid, MRef, StreamRef); + {gun_data, ConnPid, StreamRef, fin, Data} -> + io:format("fin ~s~n", [Data]); + {'DOWN', MRef, process, ConnPid, Reason} -> + error_logger:error_msg("Oops!"), + exit(Reason) + after 1000 -> + exit(timeout) + end. diff --git a/eqc/server_eqc.erl b/eqc/server_eqc.erl new file mode 100644 index 0000000000000000000000000000000000000000..7f05604d634344760d43adb84f3b8cb7f822a98f --- /dev/null +++ b/eqc/server_eqc.erl @@ -0,0 +1,1569 @@ +%%% File : server_eqc.erl +%%% Author : Ulf Norell +%%% Description : +%%% Created : 30 Mar 2020 by Ulf Norell +-module(server_eqc). + +-compile([export_all, nowarn_export_all]). + +-include_lib("eqc/include/eqc.hrl"). +-include_lib("eqc/include/eqc_component.hrl"). +-import(eqc_statem, [conj/1, tag/2]). +-include_lib("emqttc/include/emqttc_packet.hrl"). +-include_lib("roster/include/roster.hrl"). + +-define(mqtt(Payload), #mqtt_packet{payload = Payload}). + +-define(room(Handle, Name, Status, LastMsgId, LastMsg, Args), + ?CALLOUT(mock, room, [Handle, Name, Status, LastMsgId, LastMsg, Args], ok)). +-define(message(Handle, From, Msg, Status, Type, Args), ?CALLOUT(mock, message, [Handle, From, Msg, Status, Type, Args], ok)). +-define(contact(Handle, PhoneId, Status, Args), ?CALLOUT(mock, contact, [Handle, PhoneId, Status, Args], ok)). +-define(message_ack(Handle, Id, Args), ?CALLOUT(mock, message_ack, [Handle, Id, Args], ok)). + +%% -- TODO ------------------------------------------------------------------- + +%% - get_history with MsgId == 0 +%% - get_history after leaving room +%% - get_history with N == [] +%% - #'Link' (roster_link) + +%% -- State ------------------------------------------------------------------ + +-type nat() :: non_neg_integer(). +-type symbolic(A) :: A | {var, nat()}. +-type phone() :: nat(). +-type phone_id() :: binary(). +-type msg_id() :: nat(). + +-record(client, + { handle :: symbolic(pid()) + , phone :: phone() + }). + +-record(user, + { phone :: phone() + , phone_id :: symbolic(phone_id()) + , friends = #{} :: #{phone() => true} + }). + +-record(message, + { id :: symbolic(msg_id()) + , payload :: list(string()) + , visible_to = all :: all | [phone()] + , from = sys :: phone() | sys }). + +-record(contact, + { id :: binary() + , reader :: term() + , unread :: nat() + , status :: atom() }). + +-record(member, + { id :: nat() + , feed :: {muc, binary()} + , phone_id :: binary() + , reader :: nat() + , status :: atom() }). + +-record(room_info, + { name :: string() + , members :: list(#member{}) + , admins :: list(#member{}) + , readers :: list(msg_id()) + , unread :: nat() }). + +-record(feed_info, + { id :: {p2p, phone(), phone()} | {muc, binary()} + , history = [] :: [#message{}] + , seen = #{} :: #{phone() => symbolic(msg_id())} + , last_read = 0 :: symbolic(msg_id()) + , start = 0 :: symbolic(msg_id()) + , limit = #{} :: #{phone() => symbolic(msg_id())}}). + +-record(room, + { id :: binary() + , name = <<>> :: binary() %% set when room is patched otherwise == id + , members = [] :: list(phone()) + , admins = [] :: list(phone()) }). + +-record(state, + { clients = [] :: [#client{}] + , users = [] :: [#user{}] + , friend_requests = [] :: [{phone(), phone()}] + , feeds = [] :: [#feed_info{}] + , rooms = [] :: [#room{}] + }). + +initial_state() -> #state{}. + +add(Ix, X, S) -> + setelement(Ix, S, element(Ix, S) ++ [X]). + +delete(Ix, X, S) -> + setelement(Ix, S, element(Ix, S) -- [X]). + +is_client(S, H) -> + lists:keymember(H, #client.handle, S#state.clients). + +get_client(S, H) -> + lists:keyfind(H, #client.handle, S#state.clients). + +client_phone(S, H) -> + (get_client(S, H))#client.phone. + +client_phone_id(S, H) -> + user_phone_id(S, client_phone(S, H)). + +get_clients(S, Phone) -> + [ H || #client{handle = H, phone = P} <- S#state.clients, P == Phone ]. + +is_user(S, P) -> + lists:keymember(P, #user.phone, S#state.users). + +is_user_id(S, P) -> + lists:keymember(P, #user.phone_id, S#state.users). + +get_user(S, Phone) -> + lists:keyfind(Phone, #user.phone, S#state.users). + +get_user_by_id(S, PhoneId) -> + lists:keyfind(PhoneId, #user.phone_id, S#state.users). + +user_phone_id(S, Phone) -> + (get_user(S, Phone))#user.phone_id. + +phone_id_phone(S, PhoneId) -> + (get_user_by_id(S, PhoneId))#user.phone. + +add_feed(FeedId, S) -> + Feed = #feed_info{ id = FeedId }, + add(#state.feeds, Feed, S). + +get_feed(S, FeedId) -> + lists:keyfind(FeedId, #feed_info.id, S#state.feeds). + +feed_history(S, FeedId) -> + (get_feed(S, FeedId))#feed_info.history. + +feed_history(S, FeedId, Phone) -> + Feed = get_feed(S, FeedId), + History = + case maps:get(Phone, Feed#feed_info.limit, 0) of + 0 -> Feed#feed_info.history; + MsgId -> + {Hist, Rest} = lists:splitwith(fun(#message{id = Id}) -> Id > MsgId end, Feed#feed_info.history), + case Rest of + [#message{id = MsgId} = Msg | _] -> Hist ++ [Msg]; + _ -> Hist + end + end, + [ M || M <- History, visible_to(Phone, M) ]. + +in_history_limit(S, FeedId, Phone, MsgId) -> + Feed = get_feed(S, FeedId), + MsgId >= maps:get(Phone, Feed#feed_info.limit, 0). + +feed_last_msg_id(S, FeedId) -> + case feed_history(S, FeedId) of + [#message{id = Id} | _] -> Id; + [] -> 0 + end. + +feed_msg(S, FeedId, MsgId) -> + lists:keyfind(MsgId, #message.id, feed_history(S, FeedId)). + +feed_members(_S, {p2p, Phone1, Phone2}) -> + lists:usort([Phone1, Phone2]); +feed_members(S, {muc, RoomId}) -> + Room = get_room(S, RoomId), + lists:usort(Room#room.members ++ Room#room.admins). + +feed_last_seen(S, Phone, FeedId) -> + maps:get(Phone, (get_feed(S, FeedId))#feed_info.seen, 0). + +is_first_read_of_msg_id(S, {p2p, _, _} = FeedId, Phone, MsgId) -> + feed_last_seen(S, Phone, FeedId) < MsgId; +is_first_read_of_msg_id(S, FeedId, _Phone, MsgId) -> + MsgId > (get_feed(S, FeedId))#feed_info.last_read. + +user_feeds(S, Phone) -> + User = get_user(S, Phone), + Friends = maps:keys(User#user.friends) ++ [Phone], + P2Ps = [ p2p_feed(Phone, Friend) || Friend <- Friends ], + MUCs = [ {muc, N} || #room{ id = N, members = Ms, admins = As } <- S#state.rooms, + lists:member(Phone, Ms) orelse lists:member(Phone, As) ], + P2Ps ++ MUCs. + +client_feeds(S, Handle) -> + user_feeds(S, client_phone(S, Handle)). + +make_feed_id(S, {p2p, Phone1, Phone2}) -> + Id1 = user_phone_id(S, Phone1), + Id2 = user_phone_id(S, Phone2), + p2p_feed(Id1, Id2); +make_feed_id(_S, {muc, Name}) -> + {muc, Name}. + +set_feed(FeedId, NewFeed, S = #state{ feeds = Feeds }) -> + S#state{ feeds = lists:keyreplace(FeedId, #feed_info.id, Feeds, NewFeed) }. + +visible_to(Phone, #message{ visible_to = Visible }) -> + case Visible of + all -> true; + Phones -> lists:member(Phone, Phones) + end. + +intersect_visibility(all, V) -> V; +intersect_visibility(V, all) -> V; +intersect_visibility(Ps1, Ps2) -> Ps1 -- (Ps1 -- Ps2). + +feed_see_msg(FeedId, User, MsgId, S) -> + Feed0 = #feed_info{ seen = Seen } = get_feed(S, FeedId), + set_feed(FeedId, Feed0#feed_info{ seen = Seen#{ User => MsgId } }, S). + +feed_read_msg(FeedId, MsgId, S) -> + Feed0 = #feed_info{ last_read = LastMsg } = get_feed(S, FeedId), + set_feed(FeedId, Feed0#feed_info{ last_read = max(MsgId, LastMsg) }, S). + +feed_write_msg(FeedId, Msg = #message{}, S) -> + Feed0 = get_feed(S, FeedId), + set_feed(FeedId, Feed0#feed_info{ history = [Msg | Feed0#feed_info.history] }, S). + +feed_edit_msg(FeedId, #message{ id = MsgId, payload = Payload, visible_to = Vis0 }, S) -> + Feed0 = #feed_info{ history = Hist0 } = get_feed(S, FeedId), + Msg0 = lists:keyfind(MsgId, #message.id, Hist0), + Vis1 = intersect_visibility(Vis0, Msg0#message.visible_to), + Msg1 = Msg0#message{ payload = Payload, visible_to = Vis1}, + Hist1 = lists:keyreplace(MsgId, #message.id, Hist0, Msg1), + set_feed(FeedId, Feed0#feed_info{ history = Hist1 }, S). + +feed_delete_all_msgs(FeedId, Phone, MsgId, S) -> + Feed0 = #feed_info{ history = Hist0 } = get_feed(S, FeedId), + Visibility = + case FeedId of + {muc, _} -> []; + {p2p, P, P} -> []; + {p2p, _, _} -> [p2p_other_party(Phone, FeedId)] + end, + Hist1 = [ M#message{ visible_to = intersect_visibility(Visibility, M#message.visible_to) } + || M <- Hist0 ], + set_feed(FeedId, Feed0#feed_info{ history = Hist1, start = MsgId }, S). + +feed_set_history_limit(FeedId, User, HL, S) -> + Feed0 = #feed_info{ seen = Seen0, limit = Limit0 } = get_feed(S, FeedId), + History = [ M || M <- Feed0#feed_info.history, visible_to(User, M) ], + Ix = case HL of [] -> 1; [N] -> N end, + Lim = if Ix =< length(History) -> + (lists:nth(Ix, History))#message.id; + true -> Feed0#feed_info.start + end, + Seen1 = case maps:get(User, Seen0, 0) < Lim of + true -> maps:remove(User, Seen0); + false -> Seen0 + end, + Feed1 = Feed0#feed_info{ limit = Limit0#{ User => Lim }, seen = Seen1 }, + set_feed(FeedId, Feed1, S). + +on_user(Phone, S, Fun) -> + U = get_user(S, Phone), + S#state{ users = lists:keyreplace(Phone, #user.phone, S#state.users, Fun(U)) }. + +is_friend(_, Phone, Phone) -> true; +is_friend(S, Phone1, Phone2) -> + maps:is_key(Phone2, (get_user(S, Phone1))#user.friends). + +add_friend1(Phone1, Phone2, S) -> + on_user(Phone1, S, fun(U) -> U#user{ friends = (U#user.friends)#{ Phone2 => true } } end). + +add_friend(Phone1, Phone2, S) -> + Feed = p2p_feed(Phone1, Phone2), + add_friend1(Phone1, Phone2, + add_friend1(Phone2, Phone1, + add(#state.feeds, #feed_info{id = Feed, history = []}, S))). + +is_room(S, RoomId) -> + lists:keymember(RoomId, #room.id, S#state.rooms). + +get_room(S, Id) -> + lists:keyfind(Id, #room.id, S#state.rooms). + +get_room_id_by_name(S, Name) -> + case get_room(S, Name) of + false -> (lists:keyfind(Name, #room.name, S#state.rooms))#room.id; + #room{} -> Name + end. + +room_name(S, RoomId) -> + case get_room(S, RoomId) of + #room{ name = <<>> } -> RoomId; + #room{ name = Name } -> Name + end. + +room_members(S, Id) -> + #room{ members = Ms, admins = As } = get_room(S, Id), + Ms ++ As. + +add_room(RoomId, Admins, Members, S) -> + Room = #room{ id = RoomId, admins = Admins, members = Members }, + add(#state.rooms, Room, S). + +add_room_member(RoomId, Member, Role, S) -> + Room0 = get_room(S, RoomId), + Room1 = case Role of + member -> Room0#room{ members = Room0#room.members ++ [Member] }; + admin -> Room0#room{ admins = Room0#room.admins ++ [Member] } + end, + S#state{ rooms = lists:keyreplace(RoomId, #room.id, S#state.rooms, Room1) }. + +del_room_member(RoomId, Member, Role, S) -> + Room0 = get_room(S, RoomId), + Room1 = case Role of + member -> Room0#room{ members = Room0#room.members -- [Member] }; + admin -> Room0#room{ admins = Room0#room.admins -- [Member] } + end, + S#state{ rooms = lists:keyreplace(RoomId, #room.id, S#state.rooms, Room1) }. + +rename_room(OldRoom, NewRoom, S) -> + Room0 = get_room(S, OldRoom), + Room1 = Room0#room{ name = NewRoom }, + S#state{ rooms = lists:keyreplace(OldRoom, #room.id, S#state.rooms, Room1) }. + +%% -- Generators ------------------------------------------------------------- + +gen_phone() -> noshrink(choose(1, 1000)). + +gen_client(S) -> + elements([ H || #client{ handle = H } <- S#state.clients ]). + +gen_user(S) -> + elements([ P || #user{ phone = P } <- S#state.users ]). + +gen_user_id(S) -> + elements([ P || #user{ phone_id = P } <- S#state.users ]). + +gen_history_limit() -> + ?SHRINK( + frequency([{12, []}, + {5, [choose(1, 3)]}, + {1, [shrink_to(1, choose(4, 10))]}]), + [[]]). + +shrink_to(To, G) -> + ?LET(N, G, eqc_gen:shrink_int(To, N, N)). + +gen_string(Prefix) -> + noshrink(eqc_gen:fmap(fun(N) -> iolist_to_binary(io_lib:format("~s:~8.32.0b", [Prefix, N])) end, choose(0, 1 bsl 40 - 1))). + +gen_message() -> + gen_string("msg"). + +gen_room_name() -> + gen_string("room"). + +gen_message_id(S, FeedId) -> + elements([ Id || #message{id = Id} <- feed_history(S, FeedId) ] ++ [[]]). + +gen_unread_msg_id(S, Phone, FeedId) -> + Feed = get_feed(S, FeedId), + LastSeen = maps:get(Phone, Feed#feed_info.seen, 0), + elements([ Id || #message{id = Id} <- feed_history(S, FeedId), Id > LastSeen ]). + +gen_room_patch() -> + ?LET(PTs, eqc_gen:sublist([room, avatar]), + [{PT, gen_string(atom_to_list(PT))} || PT <- PTs]). + +%% -- Operations ------------------------------------------------------------- + +%% --- register_user --- + +register_args(_S) -> [gen_phone()]. + +register_pre(S, [Phone]) -> + not lists:keymember(Phone, #user.phone, S#state.users). + +register(Phone) -> + User = nynja:register_profile(make_phone(Phone)), + nynja:ws_close(User), + nynja:user_roster(User). + +register_next(S, V, [Phone]) -> + add(#state.users, #user{ phone = Phone, phone_id = V }, + add(#state.feeds, #feed_info{ id = {p2p, Phone, Phone} }, S)). + +%% --- connect --- + +connect_pre(S) -> [] /= S#state.users. + +connect_args(S) -> [elements([U#user.phone || U <- S#state.users])]. + +connect_pre(S, [Phone]) -> + lists:keymember(Phone, #user.phone, S#state.users). + +connect(Phone) -> + connection_handler(make_phone(Phone)). + +connect_next(S, Handle, [Phone]) -> + add(#state.clients, #client{ phone = Phone, handle = Handle }, S). + +connect_post(_S, [_Phone], Handle) -> + is_pid(Handle). + +%% --- disconnect --- + +disconnect_pre(S) -> [] /= S#state.clients. + +disconnect_args(S) -> [gen_client(S)]. + +disconnect_pre(S, [Handle]) -> + is_client(S, Handle). + +disconnect(Handle) -> + call_client(Handle, disconnect). + +disconnect_next(S, _V, [Handle]) -> + S#state{ clients = lists:keydelete(Handle, #client.handle, S#state.clients) }. + +%% --- get_profile --- + +get_profile_pre(S) -> [] /= S#state.clients. + +get_profile_args(S) -> [gen_client(S)]. + +get_profile_pre(S, [H]) -> + lists:keymember(H, #client.handle, S#state.clients). + +get_profile(H) -> + call_client(H, get_profile). + +get_profile_post(S, [Handle], V) -> + case V of + #{rooms := Rooms, friends := Friends} -> + Phone = client_phone(S, Handle), + conj([ check_friend(S, Phone, Friend) || Friend <- Friends ] ++ + [ check_room(S, Phone, Room) || Room <- Rooms ]); + _ -> false + end. + +check_friend(_S, _Phone, #contact{ status = Status }) + when Status == request; Status == authorization -> true; +check_friend(S, Phone, + #contact{ id = FriendId, reader = [_, Reader], unread = Unread }) -> + check_feed(S, Phone, p2p_feed(Phone, phone_id_phone(S, FriendId)), Reader, Unread). + +check_room(S, Phone, #room_info{ name = Room0, unread = Unread, admins = As, members = Ms }) -> + Room = get_room_id_by_name(S, Room0), + #member{ reader = Reader } = lists:keyfind(user_phone_id(S, Phone), #member.phone_id, As ++ Ms), + check_feed(S, Phone, {muc, Room}, Reader, Unread). + +check_feed(S, Phone, FeedId, LastRead, Unread) -> + ExpLastRead = feed_last_seen(S, Phone, FeedId), + ExpUnread = count_unread(ExpLastRead, feed_history(S, FeedId, Phone)), + conj([ tag(unread, eq(Unread, ExpUnread)), + tag(last_read, case LastRead of 0 -> true; + _ -> eq(LastRead, ExpLastRead) + end) ]). + +count_unread(MsgId, Hist) -> count_unread(MsgId, 0, Hist). + +count_unread(MsgId, N, [#message{ id = MsgId2 } | _]) when MsgId >= MsgId2 -> N; +count_unread(MsgId, N, [_ | Msgs]) -> count_unread(MsgId, N + 1, Msgs); +count_unread(_, N, []) -> N. + + +%% --- get_room_info --- + +room_info_getters(S) -> + [ {Handle, Room} + || #client{ handle = Handle, phone = Phone } <- S#state.clients, + #room{ id = Room, members = Ms, admins = As } <- S#state.rooms, + lists:member(Phone, Ms ++ As) ]. + +get_room_info_pre(S) -> + [] /= room_info_getters(S). + +get_room_info_args(S) -> + ?LET({Handle, Room}, elements(room_info_getters(S)), + [Handle, Room]). + +get_room_info_pre(S, [Handle, Room]) -> + lists:member({Handle, Room}, room_info_getters(S)). + +get_room_info(Handle, Room) -> + call_client(Handle, {get_room, room_name(Room)}). + +get_room_info_post(S, [_Handle, Room], R) -> + case R of + #room_info{ admins = Admins, + members = Members, + readers = Readers } -> + conj([tag(members, check_room_members(S, Room, Admins, Members)), + tag(readers, check_room_readers(S, Room, Readers))]); + _ -> eq(R, room) + end. + +check_room_members(S, Room, Admins, Members) -> + As = lists:sort([ phone_id_phone(S, P) + || #member{ phone_id = P, status = St } <- Admins, St /= removed ]), + Ms = lists:sort([ phone_id_phone(S, P) + || #member{ phone_id = P, status = St } <- Members, St /= removed ]), + #room{ admins = ExpAs, members = ExpMs } = get_room(S, Room), + conj([eq(As, lists:sort(ExpAs)), eq(Ms, lists:sort(ExpMs))]). + +check_room_readers(_S, _Room, _Readers) -> + true. + +%% --- send_friend_request --- + +valid_friend_requests(S) -> + [ {Handle, FriendId} + || #client{ handle = Handle, phone = Phone } <- S#state.clients, + #user{ phone = Friend, phone_id = FriendId } <- S#state.users, + Friend /= Phone, is_valid_friend_request(S, Handle, FriendId) ]. + +is_valid_friend_request(S, Handle, FriendId) -> + Phone = client_phone(S, Handle), + PhoneId = client_phone_id(S, Handle), + Friend = phone_id_phone(S, FriendId), + not is_friend(S, Phone, Friend) andalso + not lists:member({PhoneId, Friend}, S#state.friend_requests) andalso + not lists:member({FriendId, Phone}, S#state.friend_requests). + +send_friend_request_pre(S) -> + [] /= valid_friend_requests(S). + +send_friend_request_args(S) -> + ?LET({Handle, FriendId}, elements(valid_friend_requests(S)), + [Handle, FriendId]). + +send_friend_request_pre(S, [Handle, FriendId]) -> + is_client(S, Handle) andalso is_user_id(S, FriendId) andalso + is_valid_friend_request(S, Handle, FriendId). + +send_friend_request(H, Friend) -> + call_client(H, {friend_request, Friend}). + +send_friend_request_callouts(S, [H, FriendId]) -> + Phone = client_phone(S, H), + Friend = phone_id_phone(S, FriendId), + ?PAR([ ?contact(H1, '_', '_', '_') || H1 <- get_clients(S, Phone), H1 /= H ] ++ + [ ?contact(H1, '_', '_', '_') || H1 <- get_clients(S, Friend) ]). + +send_friend_request_next(S, _V, [H, Friend]) -> + add(#state.friend_requests, {client_phone_id(S, H), phone_id_phone(S, Friend)}, S). + +send_friend_request_post(_S, [_H, _Friend], V) -> + case V of + #contact{} -> true; + _ -> eq(V, contact) + end. + +%% --- accept_friend_request --- + +clients_with_pending_friend_requests(S) -> + [ {H, From} || {From, To} <- S#state.friend_requests, + H <- get_clients(S, To) ]. + +accept_friend_request_pre(S) -> + [] /= clients_with_pending_friend_requests(S). + +accept_friend_request_args(S) -> + ?LET({H, From}, elements(clients_with_pending_friend_requests(S)), [H, From]). + +accept_friend_request_pre(S, [H, Friend]) -> + lists:member({H, Friend}, clients_with_pending_friend_requests(S)). + +accept_friend_request(H, Friend) -> + call_client(H, {accept_friend, Friend}). + +accept_friend_request_callouts(S, [Handle, FriendId]) -> + Phone = client_phone(S, Handle), + Friend = phone_id_phone(S, FriendId), + Feed = p2p_feed(Phone, Friend), + ?PAR([?CALLOUTS( + ?contact(Handle, '_', '_', '_'), + ?MATCH({MsgId, Message, ok}, ?message(Handle, ?VAR, ?VAR, '_', [sys], '_')), + ?APPLY(add_friend, [Phone, FriendId, Friend]), + ?APPLY(feed_add_msg, [Feed, sys, MsgId, Message]) + )] ++ + [ ?SEQ(?contact(H1, '_', '_', '_'), + ?message(H1, '_', '_', '_', [sys], '_')) + || H1 <- get_clients(S, Phone) ++ get_clients(S, Friend), H1 /= Handle ]). + +add_friend_next(S, _V, [Phone, FriendId, Friend]) -> + delete(#state.friend_requests, {FriendId, Phone}, + add_friend(Phone, Friend, S)). + +%% --- send_message --- + +send_message_pre(S) -> [] /= S#state.clients. + +send_message_args(S) -> + ?LET(Handle, gen_client(S), + begin + Phone = client_phone(S, Handle), + Feeds = user_feeds(S, Phone), + ?LET(Feed, elements(Feeds), + [Handle, Feed, make_feed_id(S, Feed), gen_message(), bool()]) + end). + +send_message_pre(S, [Handle, Feed, _FeedId, _Message, _Ack]) -> + is_client(S, Handle) andalso + lists:member(Feed, user_feeds(S, client_phone(S, Handle))). + +send_message(Handle, _Feed, FeedId, Message, Ack) -> + call_client(Handle, {send_msg, fix_feed(FeedId), Message, Ack}). + +send_message_callouts(S, [Handle, Feed, _FeedId, Message, Ack]) -> + Phone1 = client_phone(S, Handle), + Phones = feed_members(S, Feed), + BonusMessage = element(1, Feed) == p2p andalso length(Phones) == 1 andalso [] == feed_history(S, Feed), + HistoryMessage = <<"History was removed">>, + ?PAR([ ?CALLOUTS( + ?MATCH({MsgId, ok}, ?message(Handle, ?VAR, [Message], '_', [], '_')), + ?WHEN(BonusMessage, + ?APPLY(feed_write_msg, [Feed, Phone1, sys, {call, erlang, '-', [MsgId, 1]}, [HistoryMessage]])), + ?APPLY(feed_write_msg, [Feed, Phone1, MsgId, [Message]]) )] ++ + [ ?message(H1, '_', [Message], '_', [], '_') + || Phone <- Phones, H1 <- get_clients(S, Phone), H1 /= Handle ] ++ + [ ?message(H1, '_', [HistoryMessage], '_', [sys], '_') + || BonusMessage, H1 <- get_clients(S, Phone1) ] ++ + [ ?message_ack(Handle, '_', '_') || Ack ]). + +%% --- edit_message --- + +editable_messages(S) -> + [ {Handle, Feed, MsgId} + || #client{ handle = Handle, phone = Phone } <- S#state.clients, + #feed_info{ id = Feed, history = Msgs } <- S#state.feeds, + #message{ id = MsgId, from = Phone1 } = Msg <- Msgs, + Phone == Phone1, visible_to(Phone, Msg) ]. + +edit_message_pre(S) -> + [] /= editable_messages(S). + +edit_message_args(S) -> + ?LET({Handle, Feed, MsgId}, elements(editable_messages(S)), + [Handle, Feed, make_feed_id(S, Feed), MsgId, gen_message(), false]). %% no acks?? + +edit_message_pre(S, [Handle, Feed, _FeedId, MsgId, _Message, _Ack]) -> + lists:member({Handle, Feed, MsgId}, editable_messages(S)) andalso + case Feed of + {muc, Room} -> lists:member(client_phone(S, Handle), room_members(S, Room)); + _ -> true + end. + +edit_message(Handle, _Feed, FeedId, MsgId, Message, Ack) -> + call_client(Handle, {edit_msg, fix_feed(FeedId), MsgId, Message, Ack}). + +edit_message_callouts(S, [Handle, Feed, _FeedId, MsgId0, Message, Ack]) -> + Phone1 = client_phone(S, Handle), + Phones = feed_members(S, Feed), + ?PAR([ ?CALLOUTS( + ?MATCH({MsgId, ok}, ?message(Handle, ?VAR, [Message], edit, '_', '_')), + ?APPLY(feed_edit_msg, [Feed, Phone1, MsgId0, MsgId, [Message]]) )] ++ + [ ?message(H1, '_', [Message], edit, '_', '_') + || Phone <- Phones, H1 <- get_clients(S, Phone), H1 /= Handle ] ++ + [ ?message_ack(Handle, '_', '_') || Ack ]). + +%% --- delete_message --- + +deleteable_messages(S) -> + editable_messages(S). + +delete_message_pre(S) -> + [] /= deleteable_messages(S). + +delete_message_args(S) -> + ?LET({Handle, Feed, MsgId}, elements(deleteable_messages(S)), + [Handle, Feed, make_feed_id(S, Feed), MsgId, false]). %% no acks?? + +delete_message_pre(S, [Handle, Feed, _FeedId, MsgId, _Ack]) -> + lists:member({Handle, Feed, MsgId}, deleteable_messages(S)) andalso + case Feed of + {muc, Room} -> lists:member(client_phone(S, Handle), room_members(S, Room)); + _ -> true + end. + +delete_message(Handle, _Feed, FeedId, MsgId, Ack) -> + call_client(Handle, {delete_msg, fix_feed(FeedId), MsgId, Ack}). + +delete_message_callouts(S, [Handle, Feed, _FeedId, MsgId0, Ack]) -> + Phone1 = client_phone(S, Handle), + Phones = feed_members(S, Feed), + ?PAR([ ?CALLOUTS( + ?MATCH({MsgId, ok}, ?message(Handle, ?VAR, [<<"Delete">>], delete, '_', '_')), + ?APPLY(feed_delete_msg, [Feed, Phone1, MsgId0, MsgId]) )] ++ + [ ?message(H1, '_', [<<"Delete">>], delete, '_', '_') + || Phone <- Phones, H1 <- get_clients(S, Phone), H1 /= Handle ] ++ + [ ?message_ack(Handle, '_', '_') || Ack ]). + +%% --- get_history --- + +clients_with_nonempty_feeds(S) -> + [ {Handle, Feed} + || #client{ handle = Handle, phone = Phone } <- S#state.clients, + Feed <- user_feeds(S, Phone), [] /= feed_history(S, Feed) ]. + +get_history_pre(S) -> [] /= clients_with_nonempty_feeds(S). + +%% TODO: Test with MsgId = 0 and Count = []/0 +%% - MsgId = 0 means +%% * start from top if Count < 0 +%% * start from last seen or bottom if Count > 0 +%% +%% - Count = []/0 means +%% return "all history", possibly this means including edit +%% and delete "placeholders" +get_history_args(S) -> + ?LET({Handle, Feed}, elements(clients_with_nonempty_feeds(S)), + ?LET(MsgId, gen_message_id(S, Feed), + [Handle, Feed, make_feed_id(S, Feed), MsgId, ?SUCHTHAT(N, int(), N /= 0)])). + +get_history_pre(S, [Handle, Feed, _FeedId, MsgId, _Count]) -> + lists:member({Handle, Feed}, clients_with_nonempty_feeds(S)) andalso + lists:keymember(MsgId, #message.id, feed_history(S, Feed)). + +get_history(Handle, _Feed, FeedId, MsgId, Count) -> + call_client(Handle, {get_history, fix_feed(FeedId), MsgId, Count}). + +get_history_post(S, [Handle, Feed, _FeedId, MsgId, Count], V) -> + Phone = client_phone(S, Handle), + Expect = + case in_history_limit(S, Feed, Phone, MsgId) of + false -> {error, invalid_data}; + true -> + Slice1 = + case slice(MsgId, #message.id, -Count, feed_history(S, Feed, Phone)) of + Slice0 = [#message{id = MsgId} | _] -> Slice0; + Slice0 -> + M = lists:keyfind(MsgId, #message.id, feed_history(S, Feed)), + [M#message{payload = []} | Slice0] + end, + Slice2 = + case Count < 0 of + true -> Slice1; + false -> lists:reverse(Slice1) + end, + [ M#message{ from = try user_phone_id(S, P) catch _:_ -> P end, + visible_to = all } + || M = #message{ from = P } <- Slice2 ] + end, + eq(V, Expect). + +%% --- delete_history --- + +delete_history_pre(S) -> [] /= clients_with_nonempty_feeds(S). + +delete_history_args(S) -> + ?LET({Handle, Feed}, elements(clients_with_nonempty_feeds(S)), + [Handle, Feed, make_feed_id(S, Feed)]). + +delete_history_pre(S, [Handle, Feed, _FeedId]) -> + lists:member({Handle, Feed}, clients_with_nonempty_feeds(S)). + +delete_history(Handle, _Feed, FeedId) -> + call_client(Handle, {delete_history, fix_feed(FeedId)}). + +delete_history_callouts(S, [Handle, Feed, _FeedId]) -> + Phone1 = client_phone(S, Handle), + Phones = feed_members(S, Feed), + HistoryMessage = <<"History was removed">>, + Visibility = case Feed of + {muc, _} -> all; + {p2p, _, _} -> [Phone1] + end, + ?PAR([ ?CALLOUTS( + ?MATCH({MsgId, ok}, ?message(Handle, ?VAR, [HistoryMessage], '_', [sys], '_')), + ?APPLY(feed_delete_all_msgs, [Feed, MsgId, Phone1]), + ?APPLY(feed_write_msg, [Feed, Phone1, #message{id = MsgId, visible_to = Visibility, + payload = [HistoryMessage]}]), + ?APPLY(set_history_limit_on_delete, [Feed, Phone1, MsgId]) )] ++ + [ ?message(H1, '_', [HistoryMessage], '_', [sys], '_') + || Phone <- Phones, H1 <- get_clients(S, Phone), H1 /= Handle ]). + +%% --- read_message --- + +clients_with_unread_msgs(S) -> + [ {Handle, Feed, Id} + || #client{ handle = Handle, phone = Phone } <- S#state.clients, + Feed <- user_feeds(S, Phone), + #message{ id = Id } <- feed_history(S, Feed), + feed_last_seen(S, Phone, Feed) < Id ]. + +read_message_pre(S) -> + [] /= clients_with_unread_msgs(S). + +read_message_args(S) -> + ?LET({Handle, Feed, MsgId}, elements(clients_with_unread_msgs(S)), + [Handle, Feed, make_feed_id(S, Feed), MsgId]). + +read_message_pre(S, [Handle, Feed, _FeedId, MsgId]) -> + lists:member({Handle, Feed, MsgId}, clients_with_unread_msgs(S)) andalso + lists:keymember(MsgId, #message.id, feed_history(S, Feed, client_phone(S, Handle))). + +read_message(Handle, _Feed, FeedId, MsgId) -> + call_client(Handle, {read_msg, fix_feed(FeedId), MsgId}). + +read_message_callouts(S, [Handle, Feed, _FeedId, MsgId]) -> + Phone1 = client_phone(S, Handle), + Phones = feed_members(S, Feed), + Type = element(1, Feed), + ReadConf = is_first_read_of_msg_id(S, Feed, Phone1, MsgId), + ?PAR([ ?contact(H1, '_', last_msg, '_') || H1 <- get_clients(S, Phone1), Type == p2p ] ++ + [ ?room(H1, '_', last_msg, '_', '_', '_') || H1 <- get_clients(S, Phone1), Type == muc ] ++ + [ ?message(H1, MsgId, [], '_', [read], '_') || ReadConf, Phone <- Phones, H1 <- get_clients(S, Phone) ]). + +read_message_next(S, _V, [Handle, Feed, _FeedId, MsgId]) -> + Phone = client_phone(S, Handle), + feed_see_msg(Feed, Phone, MsgId, + feed_read_msg(Feed, MsgId, S)). + +%% --- create_room --- + +create_room_pre(S) -> + [] /= S#state.clients. + +create_room_args(S) -> + ?LET(Handle, gen_client(S), + ?LET(Members0, list(gen_user_id(S)), + begin + PhoneId = client_phone_id(S, Handle), + Members = [{phone_id_phone(S, PId), PId} || PId <- lists:usort(Members0), PhoneId /= PId], + [Handle, Members, gen_room_name()] + end)). + +create_room_pre(S, [Handle, Members, _RoomName]) -> + is_client(S, Handle) andalso + begin + Phone = client_phone(S, Handle), + lists:all(fun(P) -> is_user(S, P) andalso P /= Phone end, [P || {P, _} <- Members]) + end. + +create_room(Handle, Members, RoomName) -> + call_client(Handle, {create_room, Members, room_name(RoomName)}). + +create_room_callouts(S, [Handle, Members0, Room]) -> + Phone1 = client_phone(S, Handle), + Members = [ M || {M, _} <- Members0 ], + FeedId = {muc, Room}, + ?PAR([ ?CALLOUTS( + ?MATCH({MsgId, Payload, ok}, ?room(Handle, Room, create, ?VAR, ?VAR, '_')), + ?APPLY(add_room, [Room, [Phone1], Members]), + ?APPLY(add_feed, [FeedId]), + ?APPLY(feed_write_msg, [FeedId, Phone1, sys, MsgId, Payload]) )] ++ + [ ?room(H1, '_', '_', '_', '_', '_') + || H1 <- get_clients(S, Phone1), H1 /= Handle ] ++ + [ ?room(H1, '_', '_', '_', '_', '_') + || Phone2 <- Members, Phone2 /= Phone1, + H1 <- get_clients(S, Phone2) ]). + + +%% --- patch_room --- + +patchable_rooms(S) -> + [ {Handle, Room} + || #client{ handle = Handle, phone = Phone } <- S#state.clients, + #room{ id = Room, admins = As } <- S#state.rooms, + lists:member(Phone, As) ]. + +patch_room_pre(S) -> + [] /= patchable_rooms(S). + +patch_room_args(S) -> + ?LET({Handle, Room}, elements(patchable_rooms(S)), + [Handle, Room, gen_room_patch()]). + +patch_room_pre(S, [Handle, Room, _Patch]) -> + lists:member({Handle, Room}, patchable_rooms(S)). + +patch_room(Handle, Room, Patch) -> + call_client(Handle, {patch_room, room_name(Room), Patch}), timer:sleep(100). + +patch_room_callouts(S, [Handle, RoomId, Patch]) -> + FeedId = {muc, RoomId}, + Room = room_name(S, RoomId), + Phone1 = client_phone(S, Handle), + Phones = feed_members(S, FeedId), + NewRoom = proplists:get_value(room, Patch, Room), + Renamed = NewRoom /= Room, + NewAvatar = lists:keymember(avatar, 1, Patch), + AvatarMsg = [<<"Group avatar is updated">>], + NameMsg = [<<"Group is renamed to \"", NewRoom/binary, "\"">>], + ?PAR([ ?CALLOUTS( + ?MATCH({MsgId, ok}, ?message(Handle, ?VAR, NameMsg, [], [sys], '_')), + ?APPLY(feed_write_msg, [FeedId, Phone1, sys, MsgId, NameMsg]), + ?APPLY(rename_room, [RoomId, NewRoom]) + ) || Renamed] ++ + [ ?CALLOUTS( + ?MATCH({MsgId, ok}, ?message(Handle, ?VAR, AvatarMsg, [], [sys], '_')), + ?APPLY(feed_write_msg, [FeedId, Phone1, sys, MsgId, AvatarMsg]) + ) || NewAvatar] ++ + [ ?message(H, '_', NameMsg, [], [sys], '_') + || Renamed, Phone <- Phones, H <- get_clients(S, Phone), H /= Handle ] ++ + [ ?message(H, '_', AvatarMsg, [], [sys], '_') + || NewAvatar, Phone <- Phones, H <- get_clients(S, Phone), H /= Handle ] ++ + [ ?room(H, NewRoom, patch, '_', '_', '_') + || Phone <- Phones, H <- get_clients(S, Phone) ] + ). + + +%% --- join_room --- + +joinable_clients(S) -> + [ {Handle, Room} + || #client{ handle = Handle, phone = Phone } <- S#state.clients, + #room{ id = Room, members = Ms, admins = As } <- S#state.rooms, + not lists:member(Phone, Ms ++ As) ]. + +join_room_pre(S) -> + [] /= joinable_clients(S). + +join_room_args(S) -> + ?LET({Handle, Room}, elements(joinable_clients(S)), [Handle, Room]). + +join_room_pre(S, [Handle, Room]) -> + lists:member({Handle, Room}, joinable_clients(S)). + +join_room(Handle, Room) -> + call_client(Handle, {join_room, room_name(Room)}). + +join_room_callouts(S, [Handle, RoomId]) -> + FeedId = {muc, RoomId}, + Room = room_name(S, RoomId), + Phone1 = client_phone(S, Handle), + Phones = [Phone1 | feed_members(S, FeedId)], + ?PAR([ ?CALLOUTS( + ?MATCH({MsgId, Msg, ok}, ?room(Handle, Room, join, ?VAR, ?VAR, '_')), + ?APPLY(add_room_member, [RoomId, Phone1, member]), + ?APPLY(feed_write_msg, [FeedId, Phone1, sys, MsgId, Msg]), + ?APPLY(feed_set_history_limit, [FeedId, Phone1, []]) )] ++ + [ ?room(H1, Room, join, '_', '_', '_') || Phone <- Phones, H1 <- get_clients(S, Phone), H1 /= Handle ]). + +%% --- leave_room --- + +leaveable_clients(S) -> + [ {Handle, Handle2, Room, case lists:member(Phone, As) of true -> admin; false -> member end} + || #client{ handle = Handle, phone = Phone } <- S#state.clients, + #client{ handle = Handle2, phone = Phone2 } <- S#state.clients, Phone /= Phone2, + #room{ id = Room, members = Ms, admins = As } <- S#state.rooms, + lists:member(Phone, Ms) orelse (lists:member(Phone, As) andalso length(As) > 1), + lists:member(Phone2, Ms ++ As) ]. + +leave_room_pre(S) -> + [] /= leaveable_clients(S). + +leave_room_args(S) -> + ?LET({Handle, Handle2, Room, Role}, elements(leaveable_clients(S)), [Handle, Handle2, Room, Role]). + +leave_room_pre(S, [Handle, Handle2, Room, Role]) -> + lists:member({Handle, Handle2, Room, Role}, leaveable_clients(S)). + +leave_room(Handle, _Handle2, Room, Role) -> + call_client(Handle, {leave_room, room_name(Room), Role}). + +leave_room_callouts(S, [Handle, Handle2, RoomId, Role]) -> + FeedId = {muc, RoomId}, + Room = room_name(S, RoomId), + Phone1 = client_phone(S, Handle), + Phones = feed_members(S, FeedId), + ?PAR([ ?room(H1, Room, leave, '_', '_', '_') || Phone <- Phones, H1 <- get_clients(S, Phone) ] ++ + [ ?CALLOUTS( + ?MATCH({MsgId, Msg, ok}, ?message(Handle2, ?VAR, ?VAR, '_', [sys], '_')), + ?APPLY(feed_add_msg, [FeedId, sys, MsgId, Msg]), + ?APPLY(remove_member, [RoomId, Phone1, Role]) )] ++ + [ ?message(H1, '_', '_', '_', [sys], '_') || Phone <- Phones, Phone /= Phone1, + H1 <- get_clients(S, Phone), H1 /= Handle2 ]). + +%% --- add_to_room --- + +possible_new_members(S) -> + [ {Handle, Room, Member} + || #client{ handle = Handle, phone = Phone } <- S#state.clients, + #room{ id = Room, admins = As, members = Ms } <- S#state.rooms, + lists:member(Phone, As), + #user{ phone = P, phone_id = Member } <- S#state.users, not lists:member(P, Ms ++ As) ]. + +add_to_room_pre(S) -> + [] /= possible_new_members(S). + +add_to_room_args(S) -> + ?LET({Handle, Room, Member}, elements(possible_new_members(S)), + [Handle, Room, Member, elements([member, admin]), gen_history_limit()]). + +add_to_room_pre(S, [Handle, Room, Member, _Role, _HL]) -> + lists:member({Handle, Room, Member}, possible_new_members(S)). + +add_to_room(Handle, Room, Member, Role, HL) -> + call_client(Handle, {add_to_room, room_name(Room), Member, Role, HL}). + +add_to_room_callouts(S, [Handle, RoomId, MemberId, Role, HL]) -> + FeedId = {muc, RoomId}, + Room = room_name(S, RoomId), + Phone1 = client_phone(S, Handle), + Member = phone_id_phone(S, MemberId), + Phones = [Member | feed_members(S, FeedId)], + ?PAR([ ?CALLOUTS( + ?MATCH({MsgId, Msg, ok}, ?room(Handle, Room, add, ?VAR, ?VAR, '_')), + ?APPLY(add_room_member, [RoomId, Member, Role]), + ?APPLY(feed_write_msg, [FeedId, Phone1, sys, MsgId, Msg]), + ?APPLY(feed_set_history_limit, [FeedId, Member, HL]) )] ++ + [ ?room(H1, Room, add, '_', '_', '_') || Phone <- Phones, H1 <- get_clients(S, Phone), H1 /= Handle ]). + + +%% --- remove_from_room --- + +removable_members(S) -> + [ {Handle, Room, Member, case lists:member(P, As) of true -> admin; false -> member end} + || #client{ handle = Handle, phone = Phone } <- S#state.clients, + #room{ id = Room, admins = As, members = Ms } <- S#state.rooms, + lists:member(Phone, As), + #user{ phone = P, phone_id = Member } <- S#state.users, P /= Phone, lists:member(P, Ms ++ As) ]. + +remove_from_room_pre(S) -> + [] /= removable_members(S). + +remove_from_room_args(S) -> + ?LET({Handle, Room, Member, Role}, elements(removable_members(S)), + [Handle, Room, Member, Role]). + +remove_from_room_pre(S, [Handle, Room, Member, Role]) -> + lists:member({Handle, Room, Member, Role}, removable_members(S)). + +remove_from_room(Handle, Room, Member, Role) -> + call_client(Handle, {remove_from_room, room_name(Room), Member, Role}). + +remove_from_room_callouts(S, [Handle, RoomId, MemberId, Role]) -> + FeedId = {muc, RoomId}, + Room = room_name(S, RoomId), + Phone1 = client_phone(S, Handle), + Member = phone_id_phone(S, MemberId), + Phones = feed_members(S, FeedId), + ?PAR([ ?CALLOUTS( + ?MATCH({MsgId, Msg, ok}, ?message(Handle, ?VAR, ?VAR, '_', [sys], '_')), + ?APPLY(feed_write_msg, [FeedId, Phone1, sys, MsgId, Msg]), + ?APPLY(remove_member, [RoomId, Member, Role]))] ++ + [ ?room(H1, Room, remove, '_', '_', '_') || Phone <- Phones, H1 <- get_clients(S, Phone) ] ++ + [ ?message(H1, '_', '_', '_', [sys], '_') || Phone <- Phones, H1 <- get_clients(S, Phone), H1 /= Handle ]). + +remove_member_callouts(_S, [Room, Member, Role]) -> + ?APPLY(del_room_member, [Room, Member, Role]). + +%% --- promote_member --- + +promotable_members(S) -> + [ {Handle, Room, Member} + || #client{ handle = Handle, phone = Phone } <- S#state.clients, + #room{ id = Room, admins = As, members = Ms } <- S#state.rooms, + lists:member(Phone, As), + #user{ phone = P, phone_id = Member } <- S#state.users, P /= Phone, lists:member(P, Ms) ]. + +promote_member_pre(S) -> + [] /= promotable_members(S). + +promote_member_args(S) -> + ?LET({Handle, Room, Member}, elements(promotable_members(S)), + [Handle, Room, Member]). + +promote_member_pre(S, [Handle, Room, Member]) -> + lists:member({Handle, Room, Member}, promotable_members(S)). + +promote_member(Handle, Room, Member) -> + call_client(Handle, {promote_member, room_name(Room), Member}). + +promote_member_callouts(S, [_Handle, RoomId, _Member]) -> + Phones = feed_members(S, {muc, RoomId}), + Room = room_name(S, RoomId), + ?PAR([ ?room(H1, Room, add, '_', '_', '_') || Phone <- Phones, H1 <- get_clients(S, Phone) ]). + +promote_member_next(S, _V, [_Handle, Room, MemberId]) -> + Member = phone_id_phone(S, MemberId), + Room0 = get_room(S, Room), + Room1 = Room0#room{ members = Room0#room.members -- [Member], + admins = Room0#room.admins ++ [Member] }, + S#state{ rooms = lists:keyreplace(Room, #room.id, S#state.rooms, Room1) }. + +%% --- demote_member --- + +demotable_members(S) -> + [ {Handle, Room, Member} + || #client{ handle = Handle, phone = Phone } <- S#state.clients, + #room{ id = Room, admins = As } <- S#state.rooms, + lists:member(Phone, As), + #user{ phone = P, phone_id = Member } <- S#state.users, P /= Phone, lists:member(P, As) ]. + +demote_member_pre(S) -> + [] /= demotable_members(S). + +demote_member_args(S) -> + ?LET({Handle, Room, Member}, elements(demotable_members(S)), + [Handle, Room, Member]). + +demote_member_pre(S, [Handle, Room, Member]) -> + lists:member({Handle, Room, Member}, demotable_members(S)). + +demote_member(Handle, Room, Member) -> + call_client(Handle, {demote_member, room_name(Room), Member}). + +demote_member_callouts(S, [_Handle, RoomId, _Member]) -> + Phones = feed_members(S, {muc, RoomId}), + Room = room_name(S, RoomId), + ?PAR([ ?room(H1, Room, add, '_', '_', '_') || Phone <- Phones, H1 <- get_clients(S, Phone) ]). + +demote_member_next(S, _V, [_Handle, Room, MemberId]) -> + Member = phone_id_phone(S, MemberId), + Room0 = get_room(S, Room), + Room1 = Room0#room{ members = Room0#room.members ++ [Member], + admins = Room0#room.admins -- [Member] }, + S#state{ rooms = lists:keyreplace(Room, #room.id, S#state.rooms, Room1) }. + + +%% --- delete_room --- + +deleteable_rooms(S) -> + [ Room || #room{ id = Room } <- S#state.rooms ]. + +delete_room_pre(S) -> + [] /= deleteable_rooms(S). + +delete_room_args(S) -> + [elements(deleteable_rooms(S))]. + +delete_room_pre(S, [Room]) -> + lists:member(Room, deleteable_rooms(S)). + +delete_room(Room) -> + nynja:delete_room(room_name(Room)), timer:sleep(50), ok. + +delete_room_callouts(S, [RoomId]) -> + FeedId = {muc, RoomId}, + Phones = feed_members(S, FeedId), + Room = room_name(S, RoomId), + HasMsg = [] /= [ x || #message{ from = F } <- feed_history(S, FeedId), F /= sys ], + ?PAR([?room(H, Room, delete, -1, [], '_') + || HasMsg, Phone <- Phones, H <- get_clients(S, Phone) ] ++ + [?APPLY(do_delete_room, [RoomId]) || HasMsg]). + +do_delete_room_next(S, _V, [RoomId]) -> + Room0 = get_room(S, RoomId), + Room1 = Room0#room{ members = [], admins = [] }, + S#state{ rooms = lists:keyreplace(RoomId, #room.id, S#state.rooms, Room1) }. + +%% -- Callouts -------------------------------------------------------------- + +add_room_next(S, _V, [RoomId, Admins, Members]) -> + add_room(RoomId, Admins, Members, S). + +add_feed_next(S, _V, [FeedId]) -> + add_feed(FeedId, S). + +add_room_member_next(S, _V, [Room, Phone, Role]) -> + add_room_member(Room, Phone, Role, S). + +del_room_member_next(S, _V, [Room, Phone, Role]) -> + del_room_member(Room, Phone, Role, S). + +feed_set_history_limit_next(S, _V, [FeedId, User, HL]) -> + feed_set_history_limit(FeedId, User, HL, S). + +feed_edit_msg_next(S, _V, [FeedId, Phone, MsgId0, MsgId, Payload]) -> + feed_see_msg(FeedId, Phone, MsgId, + feed_edit_msg(FeedId, #message{id = MsgId0, payload = Payload, from = Phone}, S)). + +feed_delete_msg_next(S, _V, [FeedId, Phone, MsgId0, MsgId]) -> + feed_see_msg(FeedId, Phone, MsgId, + feed_edit_msg(FeedId, #message{id = MsgId0, payload = deleted, visible_to = [], from = Phone}, S)). + +feed_delete_all_msgs_next(S, _V, [FeedId, MsgId, Phone]) -> + feed_delete_all_msgs(FeedId, Phone, MsgId, S). + +set_history_limit_on_delete_next(S, _V, [FeedId, Phone, MsgId]) -> + Feed = #feed_info{ limit = Limit } = get_feed(S, FeedId), + Phones = + case FeedId of + {p2p, _, _} -> [Phone]; + {muc, _} -> feed_members(S, FeedId) + end, + Limit1 = maps:merge(Limit, maps:from_list([{P, MsgId} || P <- Phones])), + set_feed(FeedId, Feed#feed_info{ limit = Limit1 }, S). + +feed_write_msg_next(S, V, [FeedId, Phone, MsgId, Payload]) -> + feed_write_msg_next(S, V, [FeedId, Phone, Phone, MsgId, Payload]); +feed_write_msg_next(S, V, [FeedId, Phone, From, MsgId, Payload]) -> + feed_write_msg_next(S, V, [FeedId, Phone, #message{id = MsgId, payload = Payload, from = From}]); +feed_write_msg_next(S, _V, [FeedId, Phone, Msg = #message{id = MsgId}]) -> + feed_see_msg(FeedId, Phone, MsgId, + feed_write_msg(FeedId, Msg, S)). + +feed_add_msg_next(S, _V, [FeedId, Phone, MsgId, Payload]) -> + feed_write_msg(FeedId, #message{id = MsgId, payload = Payload, from = Phone}, S). + +rename_room_next(S, _V, [OldRoom, NewRoom]) -> + rename_room(OldRoom, NewRoom, S). + +%% -- Common ----------------------------------------------------------------- + +%% postcondition_common(S, Call, V) -> +%% eq(V, return_value(S, Call)). + +weight(_, register) -> 1; +weight(_, connect) -> 3; +weight(_, disconnect) -> 1; +weight(_, get_profile) -> 1; +weight(_, get_room_info) -> 1; +weight(_, get_history) -> 2; +weight(_, delete_history) -> 1; +weight(_, accept_friend_request) -> 10; +weight(_, send_message) -> 7; +weight(_, edit_message) -> 3; +weight(_, delete_message) -> 3; +weight(_, send_friend_request) -> 4; +weight(_, read_message) -> 7; +weight(_, create_room) -> 2; +weight(_, patch_room) -> 2; +weight(S, delete_room) -> case length(S#state.rooms) > 1 of true -> 1; false -> 0 end; +weight(_, join_room) -> 4; +weight(_, add_to_room) -> 5; +weight(_, remove_from_room) -> 3; +weight(_, promote_member) -> 4; +weight(_, demote_member) -> 4; +weight(_, leave_room) -> 3. + +%% -- Connection handler ----------------------------------------------------- + +connection_handler(Phone) -> + Parent = self(), + Pid = spawn_link(fun() -> + process_flag(trap_exit, true), + User = nynja:connect_user(Phone), + Parent ! {self(), connected}, + handle_connection(Parent, User) + end), + receive + {Pid, connected} -> Pid + end. + +handle_connection(Parent, User) -> + Pid = nynja:user_pid(User), + receive + {'EXIT', Parent, _Reason} -> + nynja:ws_close(User); + stop -> + nynja:ws_close(User); + {call, Ref, From, Msg} -> + Res = handle_call(User, Msg), + From ! {reply, Ref, Res}, + handle_connection(Parent, User); + {Pid, ?mqtt(#'Contact'{phone_id = PhoneId, reader = Reader, unread = Unread, last_msg = LastMsg, status = Status})} -> + catch mock:contact(self(), PhoneId, Status, #{reader => Reader, unread => Unread}), + case LastMsg of + #'Message'{} -> mock_message(LastMsg); + _ -> ok + end, + handle_connection(Parent, User); + {Pid, ?mqtt(#'Message'{} = Message)} -> + mock_message(Message), + handle_connection(Parent, User); + {Pid, ?mqtt(#'MessageAck'{id = Id, next = Prev, feed_id = Feed})} -> + catch mock:message_ack(self(), Id, #{prev => Prev, feed => Feed}), + handle_connection(Parent, User); + {Pid, ?mqtt(#'Room'{name = Name0, status = Status, last_msg = LastMsg, admins = _As, members = _Ms})} -> + Name = + case Name0 of + [] -> <<"no name">>; + _ -> hd(binary:split(Name0, <<"-">>)) + end, + catch mock:room(self(), Name, Status, to_msg_id(LastMsg), to_msg_payload(LastMsg), #{ }), + handle_connection(Parent, User); + {Pid, ?mqtt(#io{ code = {ok, _}, data = {ok, _} })} -> + handle_connection(Parent, User); + {Pid, ?mqtt(Payload)} -> + catch mock:unexpected(self(), Payload), + handle_connection(Parent, User); + Packet -> + catch mock:unexpected(self(), Packet), + handle_connection(Parent, User) + end. + +call_client(H, Msg) -> + Ref = make_ref(), + H ! {call, Ref, self(), Msg}, + receive + {reply, Ref, Res} -> Res + end. + +mock_message(#'Message'{feed_id = Feed, prev = Prev, next = Next, type = Type, + from = From, to = To, status = Status} = Message) -> + #message{id = Id, payload = Msg} = to_message(Message), + catch mock:message(self(), Id, Msg, Status, Type, + #{from => From, feed => Feed, next => Prev, prev => Next, to => To}). + +%% -- Calls -- + +handle_call(User, get_profile) -> + ?mqtt(Profile) = nynja:get_profile1(User, 1000), + #'Profile'{ rosters = [#'Roster'{ userlist = Contacts, roomlist = Rooms }] } = Profile, + #{ rooms => [to_room(R) || R <- Rooms], friends => [to_contact(C) || C <- Contacts] }; + +handle_call(User, {get_room, Room}) -> + ?mqtt(R) = nynja:get_room(User, Room), + to_room(R); + +handle_call(User, {friend_request, PhoneId}) -> + ?mqtt(#'Contact'{} = C) = nynja:send_friend_req(User, PhoneId), + to_contact(C); + +handle_call(User, {accept_friend, PhoneId}) -> + async_send(User, nynja:friend_req_accept_msg(nynja:user_roster(User), PhoneId)); + +handle_call(User, {send_msg, Feed, Msg, Ack}) -> + To = case Feed of + {muc, Name} -> Name; + {p2p, _, _} -> p2p_other_party(nynja:user_roster(User), Feed) + end, + MsgRec = nynja:make_message(User, To, Feed, Msg, Ack), + async_send(User, MsgRec); + +handle_call(User, {edit_msg, Feed, MsgId, Msg, Ack}) -> + To = case Feed of + {muc, Name} -> Name; + {p2p, _, _} -> p2p_other_party(nynja:user_roster(User), Feed) + end, + MsgRec0 = nynja:make_message(User, To, Feed, Msg, Ack), + MsgRec1 = MsgRec0#'Message'{ status = edit, id = MsgId }, + async_send(User, MsgRec1); + +handle_call(User, {delete_msg, Feed, MsgId, Ack}) -> + To = case Feed of + {muc, Name} -> Name; + {p2p, _, _} -> p2p_other_party(nynja:user_roster(User), Feed) + end, + MsgRec0 = nynja:make_message(User, To, Feed, [], Ack), + MsgRec1 = MsgRec0#'Message'{ status = delete, id = MsgId, seenby = [-1] }, + async_send(User, MsgRec1); + +handle_call(User, {read_msg, FeedId, MsgId}) -> + ReadMsg = nynja:read_msg(User, FeedId, MsgId), + async_send(User, ReadMsg); + +handle_call(User, {get_history, Feed, MsgId, Count}) -> + case nynja:get_messages(User, Feed, MsgId, Count) of + ?mqtt(#'History'{data = Messages}) -> [ to_message(Msg) || Msg <- Messages ]; + ?mqtt(#io{code = Error}) -> Error + end; + +handle_call(User, {delete_history, FeedId}) -> + async_send(User, nynja:delete_hist(FeedId)); + +handle_call(User, {create_room, Members, RoomName}) -> + RoomId = nynja:typed_uuid(<<"room">>, RoomName), + Ms = [ nynja:make_member(#{ phone_id => PId, room_id => RoomId, status => member }) + || {_, PId} <- Members ], + As = [nynja:make_member(#{ phone_id => nynja:user_roster(User), room_id => RoomId, status => admin })], + Room = #'Room'{ id = RoomId, name = RoomName, members = Ms, admins = As, + type = group, tos = <<"EQC FTW!">>, status = create }, + async_send(User, Room); + +handle_call(User, {patch_room, RoomName, Patch}) -> + RoomId = nynja:typed_uuid(<<"room">>, RoomName), + Data = case proplists:get_value(avatar, Patch, []) of + [] -> []; + Av -> [#'Desc'{ id = Av, mime = <<"image">>, payload = Av }] + end, + NewName = proplists:get_value(room, Patch, []), + Room = #'Room'{ id = RoomId, name = NewName, data = Data, status = patch }, + async_send(User, Room); + +handle_call(User, {join_room, RoomName}) -> + Room = singleton_room(RoomName, User, member, join), + async_send(User, Room); + +handle_call(User, {leave_room, RoomName, Role}) -> + Room = singleton_room(RoomName, User, Role, leave), + async_send(User, Room); + +handle_call(User, {add_to_room, RoomName, MemberId, Role, HL}) -> + Room = (singleton_room(RoomName, MemberId, Role, add))#'Room'{readers = HL}, + async_send(User, Room); + +handle_call(User, {remove_from_room, RoomName, MemberId, Role}) -> + Room = singleton_room(RoomName, MemberId, Role, remove), + async_send(User, Room); + +handle_call(User, {promote_member, RoomName, MemberId}) -> + Room = singleton_room(RoomName, MemberId, admin, add), + async_send(User, Room); + +handle_call(User, {demote_member, RoomName, MemberId}) -> + RoomId = nynja:typed_uuid(<<"room">>, RoomName), + M = nynja:make_member(#{ phone_id => MemberId, room_id => RoomId, status => member }), + Room = singleton_room(RoomId, M, admin, add), + async_send(User, Room); + +handle_call(User, disconnect) -> + nynja:ws_close(User), + self() ! stop, %% yuck + ok. + +%% -- Property --------------------------------------------------------------- + +prop_ok() -> + ?SETUP(fun() -> setup(), fun cleanup/0 end, + with_parameter(default_process, worker, + with_parameter(color, true, + ?FORALL(Cmds, more_commands(5, commands(?MODULE)), + ?SOMETIMES(2, + begin + next_prefix(), + HSR={H, S, Res} = run_commands(?MODULE, Cmds), + timer:sleep(20), + cleanup(S), + Features = call_features(H), + pretty_commands(?MODULE, Cmds, HSR, + measure(users, length(S#state.users), + measure(clients, length(S#state.clients), + aggregate(Features, + check_command_names(?MODULE, Cmds, + Res == ok))))) + end))))). + +setup() -> eqc_mocking:start_mocking(api_spec()). +cleanup() -> ok. + +cleanup(#state{ clients = Clients }) -> + [ Pid ! stop || #client{ handle = Pid } <- Clients, is_pid(Pid) ], + ok. + +%% -- Prefix handling -------------------------------------------------------- + +%% Rerunning a test doesn't work unless we clean up the database if we use the +%% same phone numbers. To avoid this problem we prefix phone numbers by a +%% global prefix that should change between each test run. + +-define(prefix_table, server_eqc_prefix). + +get_prefix() -> + case ets:info(?prefix_table) of + undefined -> + ets:new(?prefix_table, [named_table, public, set]), + set_prefix(0); + _ -> + [{prefix, Prefix}] = ets:lookup(?prefix_table, prefix), + Prefix + end. + +set_prefix(Prefix) -> + ets:insert(?prefix_table, {prefix, Prefix}), + Prefix. + +next_prefix() -> + set_prefix(get_prefix() + 1). + +%% -- Utilities -------------------------------------------------------------- + +make_phone(N) -> + Prefix = get_prefix(), + iolist_to_binary(io_lib:format("~p~9.10.0b", [Prefix, N])). + +phone_id(Phone, RosterId) -> + UUID = phone_uuid(Phone), + iolist_to_binary([UUID, "_", integer_to_list(RosterId)]). + +room_name(Name) -> + Prefix = get_prefix(), + iolist_to_binary([Name, "-", integer_to_list(Prefix)]). + +phone_uuid(Phone) -> + nynja:phone_uuid(make_phone(Phone)). + +p2p_feed(PhoneId1, PhoneId2) -> + list_to_tuple([p2p | lists:sort([PhoneId1, PhoneId2])]). + +fix_feed({p2p, Id1, Id2}) -> + p2p_feed(Id1, Id2); +fix_feed({muc, Name}) -> + {muc, nynja:typed_uuid(<<"room">>, room_name(Name))}. + +p2p_other_party(P, {p2p, P, X}) -> X; +p2p_other_party(P, {p2p, X, P}) -> X. + +to_msg_id([]) -> -1; +to_msg_id(MsgId) when is_integer(MsgId) -> + MsgId; +to_msg_id(#'Message'{ id = MsgId }) -> + MsgId. + +to_msg_payload([]) -> []; +to_msg_payload(MsgId) when is_integer(MsgId) -> []; +to_msg_payload(#'Message'{ } = Msg) -> (to_message(Msg))#message.payload. + +to_message(#'Message'{id = Id, files = Files, from = From0, type = Type}) -> + Msg = [ Bin || #'Desc'{ mime = <<"text">>, payload = Bin } <- Files, is_binary(Bin) ], + From = case lists:member(sys, Type) of + true -> sys; + false -> From0 + end, + #message{ id = Id, payload = Msg, from = From }. + +to_room(#'Room'{ name = Name0, readers = Readers, unread = Unread, members = Members, admins = Admins }) -> + [Name | _] = binary:split(Name0, <<"-">>), + #room_info{ name = Name, members = [to_member(M) || M <- Members], + admins = [to_member(A) || A <- Admins], unread = Unread, readers = Readers }. + +to_contact(#'Contact'{ phone_id = Id, reader = Reader, unread = Unread, status = Status }) -> + #contact{ id = Id, reader = Reader, unread = Unread, status = Status }. + +to_member(#'Member'{id = Id, feed_id = Feed, phone_id = PhoneId, reader = Reader, status = Status}) -> + #member{ id = Id, feed = Feed, phone_id = PhoneId, reader = Reader, status = Status }. + +singleton_room(RoomId, Member = #'Member'{}, member, Status) -> + #'Room'{ id = RoomId, members = [Member], status = Status }; +singleton_room(RoomId, Member = #'Member'{}, admin, Status) -> + #'Room'{ id = RoomId, admins = [Member], status = Status }; +singleton_room(RoomName, MemberId, Role, Status) when is_binary(MemberId) -> + RoomId = nynja:typed_uuid(<<"room">>, RoomName), + Member = nynja:make_member(#{phone_id => MemberId, room_id => RoomId, status => Role}), + singleton_room(RoomId, Member, Role, Status); +singleton_room(RoomName, User, Role, Status) -> + singleton_room(RoomName, nynja:user_roster(User), Role, Status). + +async_send(User, Msg) -> + Packet = nynja:mqtt_publish(nynja:user_client(User), Msg), + nynja:ws_send_async(User, Packet), + ok. + +%% Result starts with Id and steps through in direction of N +%% So, slice(Id = 3, N = -2, [5, 4, 3, 2, 1]) == [3, 4] +%% and slice(Id = 3, N = 2, [5, 4, 3, 2, 1]) == [3, 2] +slice(Id, Elem, N, List) when N < 0 -> + Stop = fun(E) -> element(Elem, E) >= Id end, + slice1(Id, Stop, -N, lists:reverse(List)); +slice(Id, Elem, N, List) when N > 0 -> + Stop = fun(E) -> element(Elem, E) =< Id end, + slice1(Id, Stop, N, List). + +slice1(Id, Stop, N, [E | Tail] = List) -> + case Stop(E) of + true -> lists:sublist(List, N); + false -> slice1(Id, Stop, N, Tail) + end; +slice1(_, _, _, []) -> + []. + +%% -- API spec --------------------------------------------------------------- + +api_spec() -> + #api_spec + { mocking = eqc_mocking + , language = erlang + , modules = + [ #api_module + { name = mock + , functions = + [ #api_fun{ name = unexpected, arity = 2 } + , #api_fun{ name = contact, arity = 4 } + , #api_fun{ name = message, arity = 6 } + , #api_fun{ name = message_ack, arity = 3 } + , #api_fun{ name = room, arity = 6 } + ] + } ] }. + diff --git a/otp.mk b/otp.mk deleted file mode 100644 index 2b74dc0b770eea8de329f9747b101df8c5178ff4..0000000000000000000000000000000000000000 --- a/otp.mk +++ /dev/null @@ -1,33 +0,0 @@ -VM := vm.args -SYS := sys.config -ERL_ARGS := -args_file $(VM) -config $(SYS) -RUN_DIR ?= . -LOG_DIR ?= ./log -empty := -ROOTS := deps apps -space := $(empty) $(empty) -comma := $(empty),$(empty) -VSN := $(shell git rev-parse HEAD | head -c 6) -DATE := $(shell date "+%Y%m%d-%H%M%S") -ERL_LIBS := $(subst $(space),:,$(ROOTS)) - -clean: - rm -f .applist - mad $@ -compile: - mad $@ -.applist: - mad plan -$(RUN_DIR) $(LOG_DIR): - mkdir -p $(RUN_DIR) & mkdir -p $(LOG_DIR) -attach: - to_erl $(RUN_DIR)/ -console: .applist - ERL_LIBS=$(ERL_LIBS) erl +pc unicode $(ERL_ARGS) -eval '[application:ensure_started(A) || A <- $(shell cat .applist)]' -start: $(RUN_DIR) $(LOG_DIR) .applist - RUN_ERL_LOG_GENERATIONS=1000 RUN_ERL_LOG_MAXSIZE=20000000 \ - ERL_LIBS=$(ERL_LIBS) run_erl -daemon $(RUN_DIR)/ $(LOG_DIR)/ "exec $(MAKE) console" -stop: - @kill -9 $(shell ps ax -o pid= -o command=|grep $(RELEASE)|grep $(COOKIE)|awk '{print $$1}') - -.PHONY: compile clean console start diff --git a/priv/protobuf/service_auth/1compile.sh b/priv/protobuf/service_auth/1compile.sh deleted file mode 100755 index 6ad626dee21b25f4fa0b9b918f9631377fd75f88..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_auth/1compile.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -protoc *.proto --java_out=java diff --git a/priv/protobuf/service_auth/Auth.proto b/priv/protobuf/service_auth/Auth.proto deleted file mode 100644 index f5c0b18ecc1ce9a535bbccc356df41f8165da1e0..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_auth/Auth.proto +++ /dev/null @@ -1,30 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_auth; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "Auth.grpc"; -option java_outer_classname = "AuthCls"; -import public "authOs.proto"; -import public "Feature.proto"; -import public "authType.proto"; -import public "Service.proto"; - -message Auth { - string client_id = 1; - string dev_key = 2; - string user_id = 3; - string token = 4; - string data = 5; - authType type = 6; - int64 attempts = 7; - repeated Feature settings = 8; - repeated Service services = 9; - string push = 10; - authOs os = 11; - int64 created = 12; - int64 last_online = 13; -} diff --git a/priv/protobuf/service_auth/AuthError.proto b/priv/protobuf/service_auth/AuthError.proto deleted file mode 100644 index c75a62ed642c9674ff611ca1eb5e0887e805a646..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_auth/AuthError.proto +++ /dev/null @@ -1,17 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_auth; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "AuthError.grpc"; -option java_outer_classname = "AuthErrorCls"; -import public "Auth.proto"; -import public "authStatus.proto"; - -message AuthError { - repeated authStatus codes = 1; - Auth data = 2; -} diff --git a/priv/protobuf/service_auth/AuthService.proto b/priv/protobuf/service_auth/AuthService.proto deleted file mode 100644 index e5022911c823c2ef613d6a5c57f5266cf7467334..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_auth/AuthService.proto +++ /dev/null @@ -1,19 +0,0 @@ -syntax = "proto3"; -package service_auth; - -import "Auth.proto"; -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "AuthService.grpc"; -option java_outer_classname = "AuthServiceCls"; - -service AuthService { - rpc login (Auth) returns (Auth); - rpc resendSMS (Auth) returns (Auth); - rpc call (Auth) returns (Auth); - rpc confirm (Auth) returns (Auth); - rpc updatePushToken (Auth) returns (Auth); - rpc getSessions (Auth) returns (Auth); - rpc deleteSession (Auth) returns (Auth); - rpc deleteAllSessions (Auth) returns (Auth); -} diff --git a/priv/protobuf/service_auth/Desc.proto b/priv/protobuf/service_auth/Desc.proto deleted file mode 100644 index a1bdff122639882de16d2365361829b1f23827dc..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_auth/Desc.proto +++ /dev/null @@ -1,19 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_auth; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "Desc.grpc"; -option java_outer_classname = "DescCls"; -import public "Feature.proto"; - -message Desc { - string id = 1; - string mime = 2; - string payload = 3; - string parentid = 4; - repeated Feature data = 5; -} diff --git a/priv/protobuf/service_auth/Feature.proto b/priv/protobuf/service_auth/Feature.proto deleted file mode 100644 index 44e5d5a25170e7dd0f9c778227de975185b59a61..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_auth/Feature.proto +++ /dev/null @@ -1,17 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_auth; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "Feature.grpc"; -option java_outer_classname = "FeatureCls"; - -message Feature { - string id = 1; - string key = 2; - string value = 3; - string group = 4; -} diff --git a/priv/protobuf/service_auth/Service.proto b/priv/protobuf/service_auth/Service.proto deleted file mode 100644 index 5255c11383fad0751fad42cd288351aea27fd6ba..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_auth/Service.proto +++ /dev/null @@ -1,22 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_auth; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "Service.grpc"; -option java_outer_classname = "ServiceCls"; -import public "Feature.proto"; -import public "serverType.proto"; -import public "serverStatus.proto"; - -message Service { - string id = 1; - string data = 2; - serverType type = 3; - repeated Feature setting = 4; - int64 expiration = 5; - serverStatus service_status = 6; -} diff --git a/priv/protobuf/service_auth/Tag.proto b/priv/protobuf/service_auth/Tag.proto deleted file mode 100644 index fb29d81cb4aea95a05444d71d1cd7e9419de1629..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_auth/Tag.proto +++ /dev/null @@ -1,18 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_auth; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "Tag.grpc"; -option java_outer_classname = "TagCls"; -import public "tagType.proto"; - -message Tag { - int64 roster_id = 1; - string name = 2; - string color = 3; - tagType tag_status = 4; -} diff --git a/priv/protobuf/service_auth/authOs.proto b/priv/protobuf/service_auth/authOs.proto deleted file mode 100644 index 4308091b1208cd3bf560d0cf6e99d940110c4fad..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_auth/authOs.proto +++ /dev/null @@ -1,17 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_auth; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "authOs.grpc"; -option java_outer_classname = "authOsCls"; - -enum authOs { - ios = 0; - android = 1; - web = 2; -} - diff --git a/priv/protobuf/service_auth/authStatus.proto b/priv/protobuf/service_auth/authStatus.proto deleted file mode 100644 index 241324b6bfe8c83a39916370f4ae5aa6e6ec79b5..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_auth/authStatus.proto +++ /dev/null @@ -1,23 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_auth; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "authStatus.grpc"; -option java_outer_classname = "authStatusCls"; - -enum authStatus { - invalid_version = 0; - mismatch_user_data = 1; - number_not_allowed = 2; - session_not_found = 3; - attempts_expired = 4; - invalid_sms_code = 5; - invalid_jwt_code = 6; - permission_denied = 7; - invalid_data = 8; -} - diff --git a/priv/protobuf/service_auth/authType.proto b/priv/protobuf/service_auth/authType.proto deleted file mode 100644 index 0dbed28f069f7bd7c5bf9fbee8e348af63cca828..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_auth/authType.proto +++ /dev/null @@ -1,26 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_auth; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "authType.grpc"; -option java_outer_classname = "authTypeCls"; - -enum authType { - google_auth = 0; - facebook_auth = 1; - mobile_auth = 2; - email_auth = 3; - voice = 4; - resend = 5; - verify = 6; - push = 7; - logout = 8; - get = 9; - delete = 10; - clear = 11; -} - diff --git a/priv/protobuf/service_auth/presence.proto b/priv/protobuf/service_auth/presence.proto deleted file mode 100644 index 00f79c3f14b24783674e06115917a035e3410fe9..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_auth/presence.proto +++ /dev/null @@ -1,16 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_auth; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "presence.grpc"; -option java_outer_classname = "presenceCls"; - -enum presence { - online = 0; - offline = 1; -} - diff --git a/priv/protobuf/service_auth/serverStatus.proto b/priv/protobuf/service_auth/serverStatus.proto deleted file mode 100644 index 97d7dd002854abc3904040d78c3a5bc9e9c58a00..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_auth/serverStatus.proto +++ /dev/null @@ -1,16 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_auth; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "serverStatus.grpc"; -option java_outer_classname = "serverStatusCls"; - -enum serverStatus { - servie_verified = 0; - service_not_verified = 1; -} - diff --git a/priv/protobuf/service_auth/serverType.proto b/priv/protobuf/service_auth/serverType.proto deleted file mode 100644 index 56279a32238e9ae7b34a68269defae2d99553080..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_auth/serverType.proto +++ /dev/null @@ -1,19 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_auth; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "serverType.grpc"; -option java_outer_classname = "serverTypeCls"; - -enum serverType { - email = 0; - wallet = 1; - google_type = 2; - fb = 3; - phone = 4; -} - diff --git a/priv/protobuf/service_auth/tagType.proto b/priv/protobuf/service_auth/tagType.proto deleted file mode 100644 index 18503cdcac81525aa4dd9345f0f0bec4ee577e8e..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_auth/tagType.proto +++ /dev/null @@ -1,17 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_auth; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "tagType.grpc"; -option java_outer_classname = "tagTypeCls"; - -enum tagType { - tag_create = 0; - tag_remove = 1; - tag_edit = 2; -} - diff --git a/priv/protobuf/service_friend/1compile.sh b/priv/protobuf/service_friend/1compile.sh deleted file mode 100755 index 6ad626dee21b25f4fa0b9b918f9631377fd75f88..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_friend/1compile.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -protoc *.proto --java_out=java diff --git a/priv/protobuf/service_friend/Desc.proto b/priv/protobuf/service_friend/Desc.proto deleted file mode 100644 index cfc8b92b45579fc7ec7572b847c4cc8eb984ef3e..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_friend/Desc.proto +++ /dev/null @@ -1,19 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_friend; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "Desc.grpc"; -option java_outer_classname = "DescCls"; -import public "Feature.proto"; - -message Desc { - string id = 1; - string mime = 2; - string payload = 3; - string parentid = 4; - repeated Feature data = 5; -} diff --git a/priv/protobuf/service_friend/Feature.proto b/priv/protobuf/service_friend/Feature.proto deleted file mode 100644 index 21240771b56a93cd866c61e8229d5eaf0c5febc4..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_friend/Feature.proto +++ /dev/null @@ -1,17 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_friend; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "Feature.grpc"; -option java_outer_classname = "FeatureCls"; - -message Feature { - string id = 1; - string key = 2; - string value = 3; - string group = 4; -} diff --git a/priv/protobuf/service_friend/FriendService.proto b/priv/protobuf/service_friend/FriendService.proto deleted file mode 100644 index 8af2e80293cd9d2075003dc07d08493a3e71aa88..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_friend/FriendService.proto +++ /dev/null @@ -1,19 +0,0 @@ -syntax = "proto3"; -package service_friend; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "FriendService.grpc"; -option java_outer_classname = "FriendServiceCls"; -import public "Friend.proto"; -import public "Contact.proto"; - -service FriendService { - rpc banUser(Friend) returns (Contact); - rpc unbanContact(Friend) returns (Contact); - rpc friendRequest(Friend) returns (Contact); - rpc confirmFrienship(Friend) returns (Contact); - rpc muteContact(Friend) returns (Contact); - rpc unmuteContact(Friend) returns (Contact); - rpc ignoreRequest(Friend) returns (Contact); -} \ No newline at end of file diff --git a/priv/protobuf/service_friend/Service.proto b/priv/protobuf/service_friend/Service.proto deleted file mode 100644 index fa6c1583279207312a127499c6a9ab9a14896fc7..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_friend/Service.proto +++ /dev/null @@ -1,22 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_friend; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "Service.grpc"; -option java_outer_classname = "ServiceCls"; -import public "Feature.proto"; -import public "serverType.proto"; -import public "serverStatus.proto"; - -message Service { - string id = 1; - string data = 2; - serverType type = 3; - repeated Feature setting = 4; - int64 expiration = 5; - serverStatus service_status = 6; -} diff --git a/priv/protobuf/service_friend/Tag.proto b/priv/protobuf/service_friend/Tag.proto deleted file mode 100644 index 0bf0dea199c090e36023ba8dd3eaeae432bff754..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_friend/Tag.proto +++ /dev/null @@ -1,18 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_friend; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "Tag.grpc"; -option java_outer_classname = "TagCls"; -import public "tagType.proto"; - -message Tag { - int64 roster_id = 1; - string name = 2; - string color = 3; - tagType tag_status = 4; -} diff --git a/priv/protobuf/service_friend/presence.proto b/priv/protobuf/service_friend/presence.proto deleted file mode 100644 index a340e10d6a903ab1b8212b4b99d777c61c697029..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_friend/presence.proto +++ /dev/null @@ -1,16 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_friend; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "presence.grpc"; -option java_outer_classname = "presenceCls"; - -enum presence { - online = 0; - offline = 1; -} - diff --git a/priv/protobuf/service_friend/serverStatus.proto b/priv/protobuf/service_friend/serverStatus.proto deleted file mode 100644 index 837e7ce36722e7f4d149d9151de6cc2e7c0b5219..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_friend/serverStatus.proto +++ /dev/null @@ -1,16 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_friend; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "serverStatus.grpc"; -option java_outer_classname = "serverStatusCls"; - -enum serverStatus { - servie_verified = 0; - service_not_verified = 1; -} - diff --git a/priv/protobuf/service_friend/serverType.proto b/priv/protobuf/service_friend/serverType.proto deleted file mode 100644 index eccfaae8a537cd46934708252dc7152a7b6349f9..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_friend/serverType.proto +++ /dev/null @@ -1,19 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_friend; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "serverType.grpc"; -option java_outer_classname = "serverTypeCls"; - -enum serverType { - email = 0; - wallet = 1; - google_type = 2; - fb = 3; - phone = 4; -} - diff --git a/priv/protobuf/service_friend/tagType.proto b/priv/protobuf/service_friend/tagType.proto deleted file mode 100644 index 05a0f94a92505082f9284fb0dfc54891fd2205b6..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_friend/tagType.proto +++ /dev/null @@ -1,17 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_friend; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "tagType.grpc"; -option java_outer_classname = "tagTypeCls"; - -enum tagType { - tag_create = 0; - tag_remove = 1; - tag_edit = 2; -} - diff --git a/priv/protobuf/service_message/1compile.sh b/priv/protobuf/service_message/1compile.sh deleted file mode 100755 index 6ad626dee21b25f4fa0b9b918f9631377fd75f88..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_message/1compile.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -protoc *.proto --java_out=java diff --git a/priv/protobuf/service_message/Desc.proto b/priv/protobuf/service_message/Desc.proto deleted file mode 100644 index 08869717e1da6dc410f2c897ef46978c0a6cf8e6..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_message/Desc.proto +++ /dev/null @@ -1,19 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_message; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "Desc.grpc"; -option java_outer_classname = "DescCls"; -import public "Feature.proto"; - -message Desc { - string id = 1; - string mime = 2; - string payload = 3; - string parentid = 4; - repeated Feature data = 5; -} diff --git a/priv/protobuf/service_message/Feature.proto b/priv/protobuf/service_message/Feature.proto deleted file mode 100644 index c525cc9148869e2e972e7383845d2f915f531222..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_message/Feature.proto +++ /dev/null @@ -1,17 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_message; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "Feature.grpc"; -option java_outer_classname = "FeatureCls"; - -message Feature { - string id = 1; - string key = 2; - string value = 3; - string group = 4; -} diff --git a/priv/protobuf/service_message/History.proto b/priv/protobuf/service_message/History.proto deleted file mode 100644 index 9ec7743622212e77f87beff198fedfc2e48e2387..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_message/History.proto +++ /dev/null @@ -1,25 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_message; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "History.grpc"; -option java_outer_classname = "HistoryCls"; -import public "muc.proto"; -import public "p2p.proto"; -import public "historyType.proto"; - -message History { - string roster_id = 1; - oneof feed { - p2p feed20 = 20; - muc feed21 = 21; - } - int64 size = 3; - int64 entity_id = 4; - int64 data = 5; - historyType status = 6; -} diff --git a/priv/protobuf/service_message/Job.proto b/priv/protobuf/service_message/Job.proto deleted file mode 100644 index 0b9faf28ad69b5eac4a34ec4b869d51efbd234c0..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_message/Job.proto +++ /dev/null @@ -1,34 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_message; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "Job.grpc"; -option java_outer_classname = "JobCls"; -import public "act.proto"; -import public "jobType.proto"; -import public "Feature.proto"; -import public "Message.proto"; -import public "messageEvent.proto"; -import public "container.proto"; - -message Job { - int64 id = 1; - container container = 2; - act feed_id = 3; - int64 prev = 4; - int64 next = 5; - oneof context { - int64 context60 = 60; - string context61 = 61; - } - int64 proc = 7; - int64 time = 8; - repeated Message data = 9; - repeated messageEvent events = 10; - repeated Feature settings = 11; - jobType status = 12; -} diff --git a/priv/protobuf/service_message/Message.proto b/priv/protobuf/service_message/Message.proto deleted file mode 100644 index 838663d199308c44a6966bd3daf582877143e87f..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_message/Message.proto +++ /dev/null @@ -1,42 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_message; - -import "google/protobuf/Any.proto"; -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "Message.grpc"; -option java_outer_classname = "MessageCls"; -import public "messageType.proto"; -import public "messageStatus.proto"; -import public "muc.proto"; -import public "p2p.proto"; -import public "Desc.proto"; -import public "container.proto"; - -message Message { - int64 id = 1; - container container = 2; - oneof feed_id { - muc feed_id30 = 30; - p2p feed_id31 = 31; - } - int64 prev = 4; - int64 next = 5; - string msg_id = 6; - string from = 7; - string to = 8; - int64 created = 9; - repeated Desc files = 10; - messageType type = 11; - oneof link { - int64 link120 = 120; - Message link121 = 121; - } - repeated google.protobuf.Any seenby = 13; - repeated int64 repliedby = 14; - repeated int64 mentioned = 15; - messageStatus mstatus = 16; -} diff --git a/priv/protobuf/service_message/MessageService.proto b/priv/protobuf/service_message/MessageService.proto deleted file mode 100644 index 2663f92a57d3394a2348e01160c70a8d103e1369..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_message/MessageService.proto +++ /dev/null @@ -1,39 +0,0 @@ -syntax = "proto3"; -package service_message; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "MessageService.grpc"; -option java_outer_classname = "MessageServiceCls"; -import public "Message.proto"; -import public "History.proto"; -import public "Job.proto"; - -service MessageService { - //Message - rpc sendMessage(Message) returns (Message); - rpc delete(Message) returns (Message); - rpc deleteForAll(Message) returns (Message); - rpc replyMessage(Message) returns (Message); - rpc editMessage(Message) returns (Message); - rpc updateMessage(Message) returns (Message); - rpc translateMessage(Message) returns (Message); - rpc trancribeMessage(Message) returns (Message); - - //Job - rpc forwardMessage(Job) returns (Job); - rpc createScheduledMessage(Job) returns (Job); - rpc deleteScheduledMessage(Job) returns (Job); - rpc editScheduledMessage(Job) returns (Job); - - //History - rpc getMessage(History) returns (History); - rpc getMessagesToEndFromMessageId(History) returns (History); - rpc getAllMessages(History) returns (History); - rpc readMessage(History) returns (History); - rpc clearMessageHistory(History) returns (History); - rpc getMessageByType(History) returns (History); - rpc getMessageByTypeWithPagination(History) returns (History); - rpc getRepliedMessages(History) returns (History); - rpc getMessagesBetweenIds(History) returns (History); -} \ No newline at end of file diff --git a/priv/protobuf/service_message/Service.proto b/priv/protobuf/service_message/Service.proto deleted file mode 100644 index fd98188cc7be5d125995a0b2e392b519a4d3b882..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_message/Service.proto +++ /dev/null @@ -1,22 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_message; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "Service.grpc"; -option java_outer_classname = "ServiceCls"; -import public "Feature.proto"; -import public "serverType.proto"; -import public "serverStatus.proto"; - -message Service { - string id = 1; - string data = 2; - serverType type = 3; - repeated Feature setting = 4; - int64 expiration = 5; - serverStatus service_status = 6; -} diff --git a/priv/protobuf/service_message/Tag.proto b/priv/protobuf/service_message/Tag.proto deleted file mode 100644 index 9a1632c0de26e45ecd7114e468b20d90054a0aea..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_message/Tag.proto +++ /dev/null @@ -1,18 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_message; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "Tag.grpc"; -option java_outer_classname = "TagCls"; -import public "tagType.proto"; - -message Tag { - int64 roster_id = 1; - string name = 2; - string color = 3; - tagType tag_status = 4; -} diff --git a/priv/protobuf/service_message/act.proto b/priv/protobuf/service_message/act.proto deleted file mode 100644 index bbeec857a6062d34b966211d79f9bf6cc7f45ce5..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_message/act.proto +++ /dev/null @@ -1,18 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_message; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "act.grpc"; -option java_outer_classname = "actCls"; - -message act { - string name = 1; - oneof data { - string data20 = 20; - int64 data21 = 21; - } -} diff --git a/priv/protobuf/service_message/container.proto b/priv/protobuf/service_message/container.proto deleted file mode 100644 index 3012039d1edb6c9ce5098fc83143936696900912..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_message/container.proto +++ /dev/null @@ -1,16 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_message; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "container.grpc"; -option java_outer_classname = "containerCls"; - -enum container { - chain = 0; - cur = 1; -} - diff --git a/priv/protobuf/service_message/historyType.proto b/priv/protobuf/service_message/historyType.proto deleted file mode 100644 index e9a7eebdbf952e7a17d7d1aac53aa29bbfaac378..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_message/historyType.proto +++ /dev/null @@ -1,20 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_message; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "historyType.grpc"; -option java_outer_classname = "historyTypeCls"; - -enum historyType { - hupdated = 0; - hget = 1; - hupdate = 2; - hlast_loaded = 3; - hlast_msg = 4; - hget_reply = 5; -} - diff --git a/priv/protobuf/service_message/jobType.proto b/priv/protobuf/service_message/jobType.proto deleted file mode 100644 index c64277dc63f1f63d2ad892fc9a3e1378595a1170..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_message/jobType.proto +++ /dev/null @@ -1,20 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_message; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "jobType.grpc"; -option java_outer_classname = "jobTypeCls"; - -enum jobType { - jinit = 0; - jupdate = 1; - jdelete = 2; - jpending = 3; - jstop = 4; - jcomplete = 5; -} - diff --git a/priv/protobuf/service_message/messageEvent.proto b/priv/protobuf/service_message/messageEvent.proto deleted file mode 100644 index 72715dd79e30d0e2ca85dd30115592c0c77bf63b..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_message/messageEvent.proto +++ /dev/null @@ -1,17 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_message; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "messageEvent.grpc"; -option java_outer_classname = "messageEventCls"; - -message messageEvent { - string name = 1; - string payload = 2; - int64 timeout = 3; - string module = 4; -} diff --git a/priv/protobuf/service_message/messageStatus.proto b/priv/protobuf/service_message/messageStatus.proto deleted file mode 100644 index b514a8a72708b351b1aa00604f15bdee1ca8c475..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_message/messageStatus.proto +++ /dev/null @@ -1,19 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_message; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "messageStatus.grpc"; -option java_outer_classname = "messageStatusCls"; - -enum messageStatus { - masync = 0; - mdelete = 1; - mclear = 2; - mupdate = 3; - medit = 4; -} - diff --git a/priv/protobuf/service_message/messageType.proto b/priv/protobuf/service_message/messageType.proto deleted file mode 100644 index 5f38169131c0ff79eb9d795399262ad1c61291d9..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_message/messageType.proto +++ /dev/null @@ -1,20 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_message; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "messageType.grpc"; -option java_outer_classname = "messageTypeCls"; - -enum messageType { - sys = 0; - reply = 1; - forward = 2; - read = 3; - edited = 4; - cursor = 5; -} - diff --git a/priv/protobuf/service_message/muc.proto b/priv/protobuf/service_message/muc.proto deleted file mode 100644 index 8eb84eb4f0b68c7a58c269f215f2368d7c954896..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_message/muc.proto +++ /dev/null @@ -1,14 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_message; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "muc.grpc"; -option java_outer_classname = "mucCls"; - -message muc { - string name = 1; -} diff --git a/priv/protobuf/service_message/p2p.proto b/priv/protobuf/service_message/p2p.proto deleted file mode 100644 index 3290d952615c1f2286bb6053b11172fa81b5943f..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_message/p2p.proto +++ /dev/null @@ -1,15 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_message; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "p2p.grpc"; -option java_outer_classname = "p2pCls"; - -message p2p { - string from = 1; - string to = 2; -} diff --git a/priv/protobuf/service_message/presence.proto b/priv/protobuf/service_message/presence.proto deleted file mode 100644 index 0b33ae3cc4fa1ae3b83aac2d041653e2f4d83306..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_message/presence.proto +++ /dev/null @@ -1,16 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_message; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "presence.grpc"; -option java_outer_classname = "presenceCls"; - -enum presence { - online = 0; - offline = 1; -} - diff --git a/priv/protobuf/service_message/serverStatus.proto b/priv/protobuf/service_message/serverStatus.proto deleted file mode 100644 index 41e3eeac5f149c3de2e8d3adfb543d0b2b75894e..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_message/serverStatus.proto +++ /dev/null @@ -1,16 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_message; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "serverStatus.grpc"; -option java_outer_classname = "serverStatusCls"; - -enum serverStatus { - servie_verified = 0; - service_not_verified = 1; -} - diff --git a/priv/protobuf/service_message/serverType.proto b/priv/protobuf/service_message/serverType.proto deleted file mode 100644 index afa4a3716aaf865eaf24b1241f35a945d7eadaca..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_message/serverType.proto +++ /dev/null @@ -1,19 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_message; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "serverType.grpc"; -option java_outer_classname = "serverTypeCls"; - -enum serverType { - email = 0; - wallet = 1; - google_type = 2; - fb = 3; - phone = 4; -} - diff --git a/priv/protobuf/service_message/tagType.proto b/priv/protobuf/service_message/tagType.proto deleted file mode 100644 index f21e09717a572d79caee5933d6731fcfbc5f43e8..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_message/tagType.proto +++ /dev/null @@ -1,17 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_message; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "tagType.grpc"; -option java_outer_classname = "tagTypeCls"; - -enum tagType { - tag_create = 0; - tag_remove = 1; - tag_edit = 2; -} - diff --git a/priv/protobuf/service_profile/1compile.sh b/priv/protobuf/service_profile/1compile.sh deleted file mode 100755 index 6ad626dee21b25f4fa0b9b918f9631377fd75f88..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_profile/1compile.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -protoc *.proto --java_out=java diff --git a/priv/protobuf/service_profile/Desc.proto b/priv/protobuf/service_profile/Desc.proto deleted file mode 100644 index 9a5a2dd166166bd8cc75b78d3b673fc6b454fd92..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_profile/Desc.proto +++ /dev/null @@ -1,19 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_profile; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "Desc.grpc"; -option java_outer_classname = "DescCls"; -import public "Feature.proto"; - -message Desc { - string id = 1; - string mime = 2; - string payload = 3; - string parentid = 4; - repeated Feature data = 5; -} diff --git a/priv/protobuf/service_profile/Feature.proto b/priv/protobuf/service_profile/Feature.proto deleted file mode 100644 index 64e1725871b5a1ad01018c1343521fbc56f1d608..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_profile/Feature.proto +++ /dev/null @@ -1,17 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_profile; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "Feature.grpc"; -option java_outer_classname = "FeatureCls"; - -message Feature { - string id = 1; - string key = 2; - string value = 3; - string group = 4; -} diff --git a/priv/protobuf/service_profile/Profile.proto b/priv/protobuf/service_profile/Profile.proto deleted file mode 100644 index db8e1b3d233548f4adb1942ad1131edce8d6793b..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_profile/Profile.proto +++ /dev/null @@ -1,25 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_profile; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "Profile.grpc"; -option java_outer_classname = "ProfileCls"; -import public "Feature.proto"; -import public "profileStatus.proto"; -import public "presence.proto"; -import public "Service.proto"; - -message Profile { - string phone = 1; - repeated Service services = 2; - repeated int64 rosters = 3; - repeated Feature settings = 4; - int64 update = 5; - int64 balance = 6; - presence presence = 7; - profileStatus profileStatus = 8; -} diff --git a/priv/protobuf/service_profile/ProfileService.proto b/priv/protobuf/service_profile/ProfileService.proto deleted file mode 100644 index 64dab5f5bb7bed7414ace485b13f6d79abdb026b..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_profile/ProfileService.proto +++ /dev/null @@ -1,13 +0,0 @@ -syntax = "proto3"; -package service_profile; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "ProfileService.grpc"; -option java_outer_classname = "ProfileServiceCls"; -import public "Profile.proto"; - -service FriendService { - rpc getProfile(Profile) returns (Profile); - rpc deleteProfile(Profile) returns (Profile); -} \ No newline at end of file diff --git a/priv/protobuf/service_profile/Service.proto b/priv/protobuf/service_profile/Service.proto deleted file mode 100644 index d3af3a6ee25ff6fb7e1487a132371178c04525fc..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_profile/Service.proto +++ /dev/null @@ -1,22 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_profile; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "Service.grpc"; -option java_outer_classname = "ServiceCls"; -import public "Feature.proto"; -import public "serverType.proto"; -import public "serverStatus.proto"; - -message Service { - string id = 1; - string data = 2; - serverType type = 3; - repeated Feature setting = 4; - int64 expiration = 5; - serverStatus service_status = 6; -} diff --git a/priv/protobuf/service_profile/Tag.proto b/priv/protobuf/service_profile/Tag.proto deleted file mode 100644 index db4d5d5b4198c748ca798bac521a4aeb5afe9918..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_profile/Tag.proto +++ /dev/null @@ -1,18 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_profile; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "Tag.grpc"; -option java_outer_classname = "TagCls"; -import public "tagType.proto"; - -message Tag { - int64 roster_id = 1; - string name = 2; - string color = 3; - tagType tag_status = 4; -} diff --git a/priv/protobuf/service_profile/presence.proto b/priv/protobuf/service_profile/presence.proto deleted file mode 100644 index f37d175ce43b465495fedd5f147c8d6594e1eed0..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_profile/presence.proto +++ /dev/null @@ -1,16 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_profile; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "presence.grpc"; -option java_outer_classname = "presenceCls"; - -enum presence { - online = 0; - offline = 1; -} - diff --git a/priv/protobuf/service_profile/profileStatus.proto b/priv/protobuf/service_profile/profileStatus.proto deleted file mode 100644 index 44277b57cb56553088551c39fc443c206352e7cd..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_profile/profileStatus.proto +++ /dev/null @@ -1,17 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_profile; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "profileStatus.grpc"; -option java_outer_classname = "profileStatusCls"; - -enum profileStatus { - remove_profile = 0; - get_profile = 1; - patch_profile = 2; -} - diff --git a/priv/protobuf/service_profile/serverStatus.proto b/priv/protobuf/service_profile/serverStatus.proto deleted file mode 100644 index 1883e26bcadb3ff4d6af50a51fada0f85131b11b..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_profile/serverStatus.proto +++ /dev/null @@ -1,16 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_profile; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "serverStatus.grpc"; -option java_outer_classname = "serverStatusCls"; - -enum serverStatus { - servie_verified = 0; - service_not_verified = 1; -} - diff --git a/priv/protobuf/service_profile/serverType.proto b/priv/protobuf/service_profile/serverType.proto deleted file mode 100644 index af48113f40d7fe3996486c58dab53fb2270e36ed..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_profile/serverType.proto +++ /dev/null @@ -1,19 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_profile; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "serverType.grpc"; -option java_outer_classname = "serverTypeCls"; - -enum serverType { - email = 0; - wallet = 1; - google_type = 2; - fb = 3; - phone = 4; -} - diff --git a/priv/protobuf/service_profile/tagType.proto b/priv/protobuf/service_profile/tagType.proto deleted file mode 100644 index e7673153547d894925b862651330c2e887d9e37f..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_profile/tagType.proto +++ /dev/null @@ -1,17 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_profile; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "tagType.grpc"; -option java_outer_classname = "tagTypeCls"; - -enum tagType { - tag_create = 0; - tag_remove = 1; - tag_edit = 2; -} - diff --git a/priv/protobuf/service_room/1compile.sh b/priv/protobuf/service_room/1compile.sh deleted file mode 100755 index 6ad626dee21b25f4fa0b9b918f9631377fd75f88..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_room/1compile.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -protoc *.proto --java_out=java diff --git a/priv/protobuf/service_room/Desc.proto b/priv/protobuf/service_room/Desc.proto deleted file mode 100644 index e9a4db9b74f83347aab34670ddca9f9eaed8017b..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_room/Desc.proto +++ /dev/null @@ -1,19 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_room; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "Desc.grpc"; -option java_outer_classname = "DescCls"; -import public "Feature.proto"; - -message Desc { - string id = 1; - string mime = 2; - string payload = 3; - string parentid = 4; - repeated Feature data = 5; -} diff --git a/priv/protobuf/service_room/Feature.proto b/priv/protobuf/service_room/Feature.proto deleted file mode 100644 index 2de076cb5d05aa33adb8684bcffda5b71e7ef28a..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_room/Feature.proto +++ /dev/null @@ -1,17 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_room; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "Feature.grpc"; -option java_outer_classname = "FeatureCls"; - -message Feature { - string id = 1; - string key = 2; - string value = 3; - string group = 4; -} diff --git a/priv/protobuf/service_room/Link.proto b/priv/protobuf/service_room/Link.proto deleted file mode 100644 index a3cf002d4507f96e4b4bc9bec4f9773b354fabf1..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_room/Link.proto +++ /dev/null @@ -1,21 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_room; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "Link.grpc"; -option java_outer_classname = "LinkCls"; - -message Link { - string id = 1; - string name = 2; - string room_id = 3; - int64 created = 4; - oneof type { - } - oneof status { - } -} diff --git a/priv/protobuf/service_room/Member.proto b/priv/protobuf/service_room/Member.proto deleted file mode 100644 index fa750d3ba8e902f87262133e69f543c46618ab61..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_room/Member.proto +++ /dev/null @@ -1,41 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_room; - -import "google/protobuf/Any.proto"; -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "Member.grpc"; -option java_outer_classname = "MemberCls"; -import public "Feature.proto"; -import public "presence.proto"; -import public "muc.proto"; -import public "p2p.proto"; -import public "memberStatus.proto"; -import public "Service.proto"; -import public "container.proto"; - -message Member { - int64 id = 1; - container container = 2; - oneof feed_id { - muc feed_id30 = 30; - p2p feed_id31 = 31; - } - int64 prev = 4; - int64 next = 5; - repeated google.protobuf.Any feeds = 6; - string phone_id = 7; - string avatar = 8; - string names = 9; - string surnames = 10; - string alias = 11; - int64 reader = 12; - int64 update = 13; - repeated Feature settings = 14; - repeated Service services = 15; - presence presence = 16; - memberStatus member_status = 17; -} diff --git a/priv/protobuf/service_room/Room.proto b/priv/protobuf/service_room/Room.proto deleted file mode 100644 index 13d1f556a2853b23fdd041862cb6917accca0740..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_room/Room.proto +++ /dev/null @@ -1,40 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_room; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "Room.grpc"; -option java_outer_classname = "RoomCls"; -import public "Member.proto"; -import public "Feature.proto"; -import public "Message.proto"; -import public "Desc.proto"; - -message Room { - string id = 1; - string name = 2; - string links = 3; - string description = 4; - repeated Feature settings = 5; - repeated Member members = 6; - repeated Member admins = 7; - repeated Desc data = 8; - oneof type { - } - string tos = 10; - int64 tos_update = 11; - int64 unread = 12; - repeated int64 mentions = 13; - repeated int64 readers = 14; - oneof last_msg { - int64 last_msg150 = 150; - Message last_msg151 = 151; - } - int64 update = 16; - int64 created = 17; - oneof status { - } -} diff --git a/priv/protobuf/service_room/RoomService.proto b/priv/protobuf/service_room/RoomService.proto deleted file mode 100644 index 034e92c38f7b1de6c52e5b39ceedad72167f1b04..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_room/RoomService.proto +++ /dev/null @@ -1,23 +0,0 @@ -syntax = "proto3"; -package service_room; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "RoomService.grpc"; -option java_outer_classname = "RoomServiceCls"; -import public "Room.proto"; -import public "Member.proto"; - -service RoomService { - rpc createRoom(Room) returns (Room); - rpc leaveRoom(Room) returns (Room); - rpc kickFromRoom(Room) returns (Room); - rpc updatemRoom(Room) returns (Room); - rpc addUserToRoom(Room) returns (Room); - rpc changeUserRoleInRoom(Room) returns (Room); - rpc getRoomMembers(Room) returns (Room); - rpc clearRoomHistory(Room) returns (Room); - rpc muteRoom(Member) returns (Member); - rpc unmuteRoom(Member) returns (Member); - rpc updateMemberDetails(Member) returns (Member); -} \ No newline at end of file diff --git a/priv/protobuf/service_room/Service.proto b/priv/protobuf/service_room/Service.proto deleted file mode 100644 index 596afa5735d66b3a6f8aa3009ed201821af995c5..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_room/Service.proto +++ /dev/null @@ -1,22 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_room; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "Service.grpc"; -option java_outer_classname = "ServiceCls"; -import public "Feature.proto"; -import public "serverType.proto"; -import public "serverStatus.proto"; - -message Service { - string id = 1; - string data = 2; - serverType type = 3; - repeated Feature setting = 4; - int64 expiration = 5; - serverStatus service_status = 6; -} diff --git a/priv/protobuf/service_room/Tag.proto b/priv/protobuf/service_room/Tag.proto deleted file mode 100644 index e2d1b28d2f59fa50b598c111781af2bd5c855afa..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_room/Tag.proto +++ /dev/null @@ -1,18 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_room; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "Tag.grpc"; -option java_outer_classname = "TagCls"; -import public "tagType.proto"; - -message Tag { - int64 roster_id = 1; - string name = 2; - string color = 3; - tagType tag_status = 4; -} diff --git a/priv/protobuf/service_room/container.proto b/priv/protobuf/service_room/container.proto deleted file mode 100644 index 917329a001dc795736917f2a83318a846c92b0a0..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_room/container.proto +++ /dev/null @@ -1,16 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_room; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "container.grpc"; -option java_outer_classname = "containerCls"; - -enum container { - chain = 0; - cur = 1; -} - diff --git a/priv/protobuf/service_room/linkStatus.proto b/priv/protobuf/service_room/linkStatus.proto deleted file mode 100644 index 1ff436f8b7cc12476bbee9b61013d146eb310a6e..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_room/linkStatus.proto +++ /dev/null @@ -1,19 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_room; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "linkStatus.grpc"; -option java_outer_classname = "linkStatusCls"; - -enum linkStatus { - lgen = 0; - lcheck = 1; - ladd = 2; - ldelete = 3; - lupdate = 4; -} - diff --git a/priv/protobuf/service_room/memberStatus.proto b/priv/protobuf/service_room/memberStatus.proto deleted file mode 100644 index 34bee9bab1e829d093d204aa6d228ad6b7030e16..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_room/memberStatus.proto +++ /dev/null @@ -1,19 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_room; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "memberStatus.grpc"; -option java_outer_classname = "memberStatusCls"; - -enum memberStatus { - admin = 0; - member = 1; - removed = 2; - patch = 3; - owner = 4; -} - diff --git a/priv/protobuf/service_room/muc.proto b/priv/protobuf/service_room/muc.proto deleted file mode 100644 index 57ddef8afd34c4bac4fbe95eb665189b4acecd64..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_room/muc.proto +++ /dev/null @@ -1,14 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_room; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "muc.grpc"; -option java_outer_classname = "mucCls"; - -message muc { - string name = 1; -} diff --git a/priv/protobuf/service_room/p2p.proto b/priv/protobuf/service_room/p2p.proto deleted file mode 100644 index e0d183961c5592bde7c90b761c88bc723657d7b8..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_room/p2p.proto +++ /dev/null @@ -1,15 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_room; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "p2p.grpc"; -option java_outer_classname = "p2pCls"; - -message p2p { - string from = 1; - string to = 2; -} diff --git a/priv/protobuf/service_room/presence.proto b/priv/protobuf/service_room/presence.proto deleted file mode 100644 index bb9958e1c220bc8359b1bc504a478ae785d2330a..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_room/presence.proto +++ /dev/null @@ -1,16 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_room; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "presence.grpc"; -option java_outer_classname = "presenceCls"; - -enum presence { - online = 0; - offline = 1; -} - diff --git a/priv/protobuf/service_room/roomStatus.proto b/priv/protobuf/service_room/roomStatus.proto deleted file mode 100644 index d6c14cfdefc7abea14e4b0934d48b5b4c3df184e..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_room/roomStatus.proto +++ /dev/null @@ -1,22 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_room; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "roomStatus.grpc"; -option java_outer_classname = "roomStatusCls"; - -enum roomStatus { - room_create = 0; - room_leave = 1; - room_add = 2; - room_remove = 3; - room_patch = 4; - room_get = 5; - room_delete = 6; - room_last_msg = 7; -} - diff --git a/priv/protobuf/service_room/roomType.proto b/priv/protobuf/service_room/roomType.proto deleted file mode 100644 index 1ce70af7b7bc0aaa2f9e1d0e679bff39eda61df1..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_room/roomType.proto +++ /dev/null @@ -1,16 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_room; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "roomType.grpc"; -option java_outer_classname = "roomTypeCls"; - -enum roomType { - group = 0; - channel = 1; -} - diff --git a/priv/protobuf/service_room/serverStatus.proto b/priv/protobuf/service_room/serverStatus.proto deleted file mode 100644 index 045d8ed0e6398ae96a789eb4be7fe94a18eea87b..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_room/serverStatus.proto +++ /dev/null @@ -1,16 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_room; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "serverStatus.grpc"; -option java_outer_classname = "serverStatusCls"; - -enum serverStatus { - servie_verified = 0; - service_not_verified = 1; -} - diff --git a/priv/protobuf/service_room/serverType.proto b/priv/protobuf/service_room/serverType.proto deleted file mode 100644 index eeefc6b5cf0c7636f541b841362890e13bdc18a4..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_room/serverType.proto +++ /dev/null @@ -1,19 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_room; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "serverType.grpc"; -option java_outer_classname = "serverTypeCls"; - -enum serverType { - email = 0; - wallet = 1; - google_type = 2; - fb = 3; - phone = 4; -} - diff --git a/priv/protobuf/service_room/tagType.proto b/priv/protobuf/service_room/tagType.proto deleted file mode 100644 index c0f05076e5560cb938673108fc12ea211649a296..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_room/tagType.proto +++ /dev/null @@ -1,17 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_room; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "tagType.grpc"; -option java_outer_classname = "tagTypeCls"; - -enum tagType { - tag_create = 0; - tag_remove = 1; - tag_edit = 2; -} - diff --git a/priv/protobuf/service_roster/1compile.sh b/priv/protobuf/service_roster/1compile.sh deleted file mode 100755 index 6ad626dee21b25f4fa0b9b918f9631377fd75f88..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_roster/1compile.sh +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/sh - -protoc *.proto --java_out=java diff --git a/priv/protobuf/service_roster/Contact.proto b/priv/protobuf/service_roster/Contact.proto deleted file mode 100644 index 8fcb3eec68396638ffd6bec97b6d78ec62432370..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_roster/Contact.proto +++ /dev/null @@ -1,32 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_roster; - -import "google/protobuf/Any.proto"; -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "Contact.grpc"; -option java_outer_classname = "ContactCls"; -import public "Feature.proto"; -import public "contactStatus.proto"; -import public "presence.proto"; -import public "Service.proto"; - -message Contact { - string user_id = 1; - repeated int64 avatar = 2; - string names = 3; - string surnames = 4; - string nick = 5; - repeated google.protobuf.Any reader = 6; - int64 unread = 7; - int64 last_msg = 8; - int64 update = 9; - int64 created = 10; - repeated Feature settings = 11; - repeated Service services = 12; - presence presence = 13; - contactStatus status = 14; -} diff --git a/priv/protobuf/service_roster/Desc.proto b/priv/protobuf/service_roster/Desc.proto deleted file mode 100644 index 99c421db4a2cc404394a5ca6f59c76a9ff1968d8..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_roster/Desc.proto +++ /dev/null @@ -1,19 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_roster; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "Desc.grpc"; -option java_outer_classname = "DescCls"; -import public "Feature.proto"; - -message Desc { - string id = 1; - string mime = 2; - string payload = 3; - string parentid = 4; - repeated Feature data = 5; -} diff --git a/priv/protobuf/service_roster/Feature.proto b/priv/protobuf/service_roster/Feature.proto deleted file mode 100644 index 3859dcf675244820ed48253043efa88c789b12ca..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_roster/Feature.proto +++ /dev/null @@ -1,17 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_roster; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "Feature.grpc"; -option java_outer_classname = "FeatureCls"; - -message Feature { - string id = 1; - string key = 2; - string value = 3; - string group = 4; -} diff --git a/priv/protobuf/service_roster/Roster.proto b/priv/protobuf/service_roster/Roster.proto deleted file mode 100644 index 956afea8e247d9b5f5918227e60199e76cc2b77e..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_roster/Roster.proto +++ /dev/null @@ -1,28 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_roster; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "Roster.grpc"; -option java_outer_classname = "RosterCls"; -import public "Contact.proto"; -import public "rosterStatus.proto"; - -message Roster { - int64 id = 1; - string names = 2; - string surnames = 3; - string email = 4; - string nick = 5; - repeated Contact userlist = 6; - repeated int64 roomlist = 7; - repeated int64 favorite = 8; - repeated int64 tags = 9; - string phone = 10; - string avatar = 11; - int64 update = 12; - rosterStatus rosterStatus = 13; -} diff --git a/priv/protobuf/service_roster/RosterService.proto b/priv/protobuf/service_roster/RosterService.proto deleted file mode 100644 index abf822cc920a3757c7a074fcfd3c87f96a439345..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_roster/RosterService.proto +++ /dev/null @@ -1,14 +0,0 @@ -syntax = "proto3"; -package service_roster; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "RosterService.grpc"; -option java_outer_classname = "RosterServiceCls"; -import public "Roster.proto"; -import public "Contact.proto"; - -service RosterService { - rpc update(Roster) returns (Roster); - rpc updateNick(Roster) returns (Roster); -} \ No newline at end of file diff --git a/priv/protobuf/service_roster/Service.proto b/priv/protobuf/service_roster/Service.proto deleted file mode 100644 index 180788cd3120cf232a2ae3d76bfd8b31d2512d8c..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_roster/Service.proto +++ /dev/null @@ -1,22 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_roster; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "Service.grpc"; -option java_outer_classname = "ServiceCls"; -import public "Feature.proto"; -import public "serverType.proto"; -import public "serverStatus.proto"; - -message Service { - string id = 1; - string data = 2; - serverType type = 3; - repeated Feature setting = 4; - int64 expiration = 5; - serverStatus service_status = 6; -} diff --git a/priv/protobuf/service_roster/Tag.proto b/priv/protobuf/service_roster/Tag.proto deleted file mode 100644 index 8fc287a05eaaad8fd0c198be1a9957ddd4e3e28f..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_roster/Tag.proto +++ /dev/null @@ -1,18 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_roster; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "Tag.grpc"; -option java_outer_classname = "TagCls"; -import public "tagType.proto"; - -message Tag { - int64 roster_id = 1; - string name = 2; - string color = 3; - tagType tag_status = 4; -} diff --git a/priv/protobuf/service_roster/contactStatus.proto b/priv/protobuf/service_roster/contactStatus.proto deleted file mode 100644 index be8237beaf778b4d4c5886301b1b5b8a5105f1ef..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_roster/contactStatus.proto +++ /dev/null @@ -1,23 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_roster; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "contactStatus.grpc"; -option java_outer_classname = "contactStatusCls"; - -enum contactStatus { - conact_request = 0; - authorization = 1; - contact_ignore = 2; - conatct_internal = 3; - friend = 4; - contact_last_msg = 5; - contact_ban = 6; - conact_banned = 7; - contact_deleted = 8; -} - diff --git a/priv/protobuf/service_roster/muc.proto b/priv/protobuf/service_roster/muc.proto deleted file mode 100644 index 039c4d5224bc7eb1da2776ef8655526ef97bc6de..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_roster/muc.proto +++ /dev/null @@ -1,14 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_roster; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "muc.grpc"; -option java_outer_classname = "mucCls"; - -message muc { - string name = 1; -} diff --git a/priv/protobuf/service_roster/p2p.proto b/priv/protobuf/service_roster/p2p.proto deleted file mode 100644 index 46a95b911cc13c55a16713e889979ea363ab62c1..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_roster/p2p.proto +++ /dev/null @@ -1,15 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_roster; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "p2p.grpc"; -option java_outer_classname = "p2pCls"; - -message p2p { - string from = 1; - string to = 2; -} diff --git a/priv/protobuf/service_roster/presence.proto b/priv/protobuf/service_roster/presence.proto deleted file mode 100644 index 3c2a5c119cf0afea9d4a01b3029a5cd329a15002..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_roster/presence.proto +++ /dev/null @@ -1,16 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_roster; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "presence.grpc"; -option java_outer_classname = "presenceCls"; - -enum presence { - online = 0; - offline = 1; -} - diff --git a/priv/protobuf/service_roster/rosterStatus.proto b/priv/protobuf/service_roster/rosterStatus.proto deleted file mode 100644 index 65c391024ba14d93be03838e546653f8e0955635..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_roster/rosterStatus.proto +++ /dev/null @@ -1,24 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_roster; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "rosterStatus.grpc"; -option java_outer_classname = "rosterStatusCls"; - -enum rosterStatus { - get_roster = 0; - create_roster = 1; - del_roster = 2; - remove_roster = 3; - nick = 4; - add_roster = 5; - update_roster = 6; - list_loster = 7; - patch_roster = 8; - roster_last_msg = 9; -} - diff --git a/priv/protobuf/service_roster/serverStatus.proto b/priv/protobuf/service_roster/serverStatus.proto deleted file mode 100644 index 57d0314df734039ae7f2f61934c06d9bde997567..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_roster/serverStatus.proto +++ /dev/null @@ -1,16 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_roster; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "serverStatus.grpc"; -option java_outer_classname = "serverStatusCls"; - -enum serverStatus { - servie_verified = 0; - service_not_verified = 1; -} - diff --git a/priv/protobuf/service_roster/serverType.proto b/priv/protobuf/service_roster/serverType.proto deleted file mode 100644 index 7ca622a890869f2b71d6dafec6d7f9cdac7bfc7d..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_roster/serverType.proto +++ /dev/null @@ -1,19 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_roster; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "serverType.grpc"; -option java_outer_classname = "serverTypeCls"; - -enum serverType { - email = 0; - wallet = 1; - google_type = 2; - fb = 3; - phone = 4; -} - diff --git a/priv/protobuf/service_roster/tagType.proto b/priv/protobuf/service_roster/tagType.proto deleted file mode 100644 index 73981ba16a735570949d6021bd25098ad4fcc555..0000000000000000000000000000000000000000 --- a/priv/protobuf/service_roster/tagType.proto +++ /dev/null @@ -1,17 +0,0 @@ -// Generated by https://github.com/synrc/bert -// DO NOT EDIT - -syntax = "proto3"; -package service_roster; - -option java_generic_services = true; -option java_multiple_files = true; -option java_package = "tagType.grpc"; -option java_outer_classname = "tagTypeCls"; - -enum tagType { - tag_create = 0; - tag_remove = 1; - tag_edit = 2; -} - diff --git a/rebar.config b/rebar.config index 7f954532fc4db2833c18e91916ec641029b71b32..ff04d6912c50d34feabef46fd45a06381595527c 100644 --- a/rebar.config +++ b/rebar.config @@ -1,51 +1,101 @@ -{sub_dirs,["apps"]}. -{lib_dirs,["apps","deps"]}. -{deps_dir,"deps"}. +%% -*- mode:erlang; erlang-indent-level: 4; indent-tabs-mode: nil -*- +{erl_opts, [{parse_transform, oc_span_transform}, + debug_info, + warnings_as_errors, + nowarn_export_all + ]}. + {deps, [ - {bert, ".*", {git, "git://github.com/synrc/bert",{tag,"2.4"}}}, - {active, ".*", {git, "git://github.com/synrc/active",{tag,"master"}}}, - {esockd, ".*", {git, "https://github.com/voxoz/esockd",{ref, "a80634b961c315ffe5f020d73236473b53ae5dc9"}}}, - {bpe, ".*", {git, "git://github.com/synrc/bpe", {tag,"4.4"}}}, - {emqttd, ".*", {git, "git://github.com/NYNJA-MC/emqttd",{tag,"master"}}}, - {n2o, ".*", {git, "git://github.com/synrc/n2o", {tag,"v6.4"}}}, - {emqttc, ".*", {git, "git://github.com/NYNJA-MC/emqttc",{tag,"master"}}}, - {rest, ".*", {git, "git://github.com/synrc/rest",{tag,"5.10"}}}, - {gen_smtp, ".*", {git, "git://github.com/voxoz/gen_smtp",{tag,"master"}}}, - {emq_dashboard, ".*", {git, "https://github.com/synrc/emq_dashboard",{tag,"master"}}}, - {'opencensus-erlang', ".*", {git, "https://github.com/voxoz/opencensus-erlang",{tag, "v0.4.0"}}}, - {libphonenumber_erlang, ".*", {git, "https://github.com/marinakr/libphonenumber_erlang.git",{tag,"master"}}}, - {gproc, ".*", {git, "https://github.com/uwiger/gproc","0.6.1"}}, - {erlydtl, ".*", {git, "git://github.com/voxoz/erlydtl",{tag,"master"}}}, - {mini_s3, ".*", {git, "https://github.com/chef/mini_s3.git",{tag,"master"}}}, - {jwt, ".*", {git, "https://github.com/artemeff/jwt.git",{tag, "0.1.8"}}}, - {jsx, ".*", {git, "https://github.com/talentdeficit/jsx.git","v2.9.0"}}, - {base64url, ".*", {git, "https://github.com/dvv/base64url.git","v1.0"}}, - {migresia, ".*", {git, "https://github.com/yoonka/migresia.git",{tag,"master"}}}, - {counters, ".*", {git, "https://github.com/deadtrickster/counters.erl.git", {tag, "v0.2.0"}}}, - {ctx, ".*", {git, "https://github.com/tsloughter/ctx.git", []}}, - {wts, ".*", {git, "https://github.com/tsloughter/wts.git", []}}, - {rfc3339, ".*", {git, "https://github.com/talentdeficit/rfc3339",{tag,"0.2.2"}}}, - {locus, ".*", {git, "https://github.com/g-andrade/locus.git",{ref,"0ea9079ce5686573e0e70e2b1311343dd25feef8"}}}, - {prometheus, ".*", {git, "https://github.com/deadtrickster/prometheus.erl",{tag,"master"}}}, - {cowboy, ".*", {git, "git://github.com/voxoz/cowboy", {tag,"master"}}}, - {'jose-erlang', ".*", {git, "https://github.com/manifest/jose-erlang.git", {tag,"master"}}}, - {json_rec, ".*", {git, "https://github.com/justinkirby/json_rec.git", {tag,"master"}}}, - {'erlang-uuid', ".*", {git, "https://github.com/avtobiff/erlang-uuid.git",{tag,"master"}}}, - {enenra, ".*", {git, "https://github.com/nlfiedler/enenra", {tag, "0.3.0"}}}, - {'qdate', ".*", {git, "https://github.com/enterprizing/qdate.git",{ref,"fba988fc54214bb37a3ce11d5c5a3cc68752c3ce"}}} + {active, {git, "git://github.com/synrc/active", {branch,"master"}}}, + {bert, {git, "git://github.com/NYNJA-MC/bert.git", {branch, master}}}, + {esockd, {git, "https://github.com/voxoz/esockd", {branch, "master"}}}, + {bpe, {git, "git://github.com/synrc/bpe", {tag,"4.4"}}}, + {emqttd, {git, "git://github.com/NYNJA-MC/emqttd", {branch,"master"}}}, + {n2o, {git, "git://github.com/NYNJA-MC/n2o", {branch,"v6.4"}}}, + {emqttc, {git, "git://github.com/NYNJA-MC/emqttc", {branch,"master"}}}, + {rest, {git, "git://github.com/synrc/rest", {tag,"5.10"}}}, + {gen_smtp, {git, "git://github.com/voxoz/gen_smtp", {branch,"master"}}}, + {emq_dashboard, {git, "https://github.com/synrc/emq_dashboard", {branch,"master"}}}, + {opencensus, {git, "https://github.com/census-instrumentation/opencensus-erlang", {ref, "7fb276f"}}}, + {libphonenumber_erlang, {git, "https://github.com/marinakr/libphonenumber_erlang.git", {branch,"master"}}}, + {gproc, {git, "https://github.com/uwiger/gproc", {tag, "0.6.1"}}}, + {erlydtl, {git, "git://github.com/voxoz/erlydtl", {branch,"master"}}}, + {mini_s3, {git, "https://github.com/chef/mini_s3.git", {branch,"master"}}}, + {jwt, {git, "https://github.com/artemeff/jwt.git", {tag, "0.1.8"}}}, + {jsx, {git, "https://github.com/talentdeficit/jsx.git",{tag, "v2.9.0"}}}, + {base64url, {git, "https://github.com/dvv/base64url.git", {tag, "v1.0"}}}, + {migresia, {git, "https://github.com/yoonka/migresia.git", {branch,"master"}}}, + {counters, {git, "https://github.com/deadtrickster/counters.erl.git", {tag, "v0.2.2"}}}, + {ctx, {git, "https://github.com/tsloughter/ctx.git"}}, + {wts, {git, "https://github.com/tsloughter/wts.git"}}, + {rfc3339, {git, "https://github.com/talentdeficit/rfc3339",{tag,"0.2.2"}}}, + {locus, {git, "https://github.com/g-andrade/locus.git", {ref,"0ea9079ce5686573e0e70e2b1311343dd25feef8"}}}, + {prometheus, {git, "https://github.com/deadtrickster/prometheus.erl", {branch,"master"}}}, + {cowboy, {git, "git://github.com/ninenines/cowboy", {tag,"2.7.0"}}}, + {jose, {git, "https://github.com/NYNJA-MC/jose-erlang.git", {ref, "7094018"}}}, + {uuid, {git, "https://github.com/avtobiff/erlang-uuid.git",{branch,"master"}}}, + {enenra, {git, "https://github.com/nlfiedler/enenra", {tag, "0.3.0"}}}, + {'qdate', {git, "https://github.com/enterprizing/qdate.git", {ref,"fba988fc54214bb37a3ce11d5c5a3cc68752c3ce"}}} ]}. -{erl_opts, [{parse_transform,lager_transform},{parse_transform, oc_transform},debug_info]}. -{relx, [{release, {server, "1.0.0"}, - [roster, service]}, - {sys_config, "./sys.config"}, - {vm_args, "./vm.args"}, - {dev_mode, true}, - {include_erts, false}, - {extended_start_script, true} - ] - }. - {profiles, [ - {prod, [{relx, [{dev_mode, false}, - {include_erts, true}]} - ]} - ]}. + + +{profiles, [{local, [{relx, [{dev_mode, true}, + {include_erts, false}, + {include_src, true}]} + ]}, + {prod, [{relx, [{dev_mode, false}, + {include_erts, true}, + {include_src, true}, + {overlay, []} + ]} + ]}, + {test, [{deps, [{gun, {git, "https://github.com/ninenines/gun", {tag, "1.3.2"}}}, + {uuid, {git, "https://github.com/okeuday/uuid.git", {tag, "v1.7.5"}}}, + {jesse, {git, "https://github.com/for-GET/jesse.git", {branch, "master"}}} + ]} + ]} + ]}. + +{ct_opts, [{sys_config, ["./sys.config"]}]}. + +%% Generate json, javascript and swift in following way +{overrides, [{add, service, [{erl_opts, [{bert_proto_dir, "priv"}, + {bert_disallowed, ['feed', 'Whitelist', 'FakeNumbers','Schedule','Index','process']} + ]}]}, + {add, roster, [{erl_opts, [{bert_erl, "priv/src"}, + {bert_js, "priv/macbert"}, + {bert_swift, "priv/macbert"}, + {bert_allowed_hrl, ["roster"]}, + {bert_disallowed, ['feed', 'Whitelist', 'FakeNumbers','Schedule','Index','process']} + ]}]} + ]}. + +{artifacts, ["lib/roster/src/roster_validator.erl", "lib/roster/ebin/roster_validator.beam"]}. + +{relx, [{release, {server, "1.0.3"}, + %% Copied from .applist in older version. Should be cleaned up. + [kernel, stdlib, sasl, crypto, inets, os_mon, + fs, gproc, gen_logger, compiler, + mnesia, kvs, + esockd, + prometheus,bert,n2o, + metrics,mimerl,unicode_util_compat,base64url,jsx,tools, + certifi,ibrowse,asn1,xmerl,counters,ctx, + wts,syntax_tools,qdate_localtime, + libphonenumber_erlang,syn,cowlib,jiffy,idna,parse_trans, + goldrush, public_key,bpe,lager,ssl,ranch, + ssl_verify_fun,locus,emqttd,hackney,roster,service,active, + cowboy,emq_dashboard,emqttc,enenra,envy,uuid,erlydtl,forms, + gen_smtp, jwt, migresia, mini_s3, nitro, opencensus, + qdate,rest,rfc3339,sh,stacktrace_compat]}, + {sys_config, "./sys.config"}, + {vm_args, "./vm.args"}, + {dev_mode, true}, + {include_erts, false}, + {extended_start_script, true}, + {overlay, [{copy, "admin", "./admin"}, + {copy, "etc", "./etc"}, + {copy, "./priv", "priv"}, + {copy, "asserts", "./asserts"} + ]} + ]}. diff --git a/rebar.lock b/rebar.lock index 4c14c39199282f1b3a75df2473dd89363df8636a..5cfe2b4c2009ba7d989ca5514fe2c191645a8a47 100644 --- a/rebar.lock +++ b/rebar.lock @@ -1,68 +1,78 @@ {"1.1.0", -[{<<"base64url">>, +[{<<"active">>, + {git,"git://github.com/synrc/active", + {ref,"cdd8f2b0f62b9785673bdbea7be90e1ae1ca1c02"}}, + 0}, + {<<"base64url">>, {git,"https://github.com/dvv/base64url.git", {ref,"f2c64ed8b9bebc536fad37ad97243452b674b837"}}, 0}, {<<"bert">>, - {git,"git://github.com/synrc/bert", - {ref,"6cde41b32448e85ecd48225221d6f978943a1bad"}}, + {git,"git://github.com/NYNJA-MC/bert.git", + {ref,"9a0cd97b6db86852811947888424236b25b451c9"}}, 0}, {<<"bpe">>, {git,"git://github.com/synrc/bpe", {ref,"356c9e621c38e927a8611ecac592bcdc8d689026"}}, 0}, {<<"certifi">>,{pkg,<<"certifi">>,<<"2.4.2">>},1}, + {<<"cf">>,{pkg,<<"cf">>,<<"0.3.1">>},2}, {<<"counters">>, {git,"https://github.com/deadtrickster/counters.erl.git", - {ref,"c3f4aa3acdf71c3db0a4b3fc1343aa45de2c5df0"}}, + {ref,"70e709ef43ba0f6ee28e31efe5894eaa475211e1"}}, 0}, {<<"cowboy">>, - {git,"git://github.com/voxoz/cowboy", - {ref,"c1cfbfa5dc6b5f6ecd9591ad5bf642b6a107a7f5"}}, + {git,"git://github.com/ninenines/cowboy", + {ref,"63b17e4edf666d995ec86cdcda17a62ba5ebc423"}}, 0}, {<<"cowlib">>, - {git,"git://github.com/voxoz/cowlib", - {ref,"5cc0038d0a3ae6a829646ddfff998d8c491969ca"}}, + {git,"https://github.com/ninenines/cowlib", + {ref,"c6553f8308a2ca5dcd69d845f0a7d098c40c3363"}}, 1}, {<<"ctx">>, {git,"https://github.com/tsloughter/ctx.git", - {ref,"a5a6b0948708e02bc7b9cb6248807c9ff8327940"}}, + {ref,"91c892b51d3340fc004099e15e4e029a5ec0e098"}}, 0}, {<<"emq_dashboard">>, {git,"https://github.com/synrc/emq_dashboard", - {ref,"f711e8d2b0a992f5540123f3504eebabc324a684"}}, + {ref,"3317c7dd47b07d28d7dbea4de971b48fc042fefd"}}, 0}, {<<"emqttc">>, - {git,"git://github.com/voxoz/emqttc", - {ref,"141fd2e925be854321d22005313762adb1195f48"}}, + {git,"git://github.com/NYNJA-MC/emqttc", + {ref,"1ffeaf64791b76faa18dad9e9f40686814d5dd10"}}, 0}, {<<"emqttd">>, - {git,"git://github.com/synrc/emqttd", - {ref,"c3d0b7b092fffe7bebe27af27dbbfe1714757d1c"}}, + {git,"git://github.com/NYNJA-MC/emqttd", + {ref,"5e2342bf7ae597804a6f9234a54cad2f6b5761d1"}}, + 0}, + {<<"enenra">>, + {git,"https://github.com/nlfiedler/enenra", + {ref,"ccc2553b8df5a188cdc77aeec7b856a1e5250e67"}}, 0}, {<<"envy">>, {git,"https://github.com/markan/envy.git", {ref,"0148fb4b7ed0e188511578e98b42d6e7dde0ebd1"}}, 1}, - {<<"erlang-uuid">>, - {git,"https://github.com/avtobiff/erlang-uuid.git", - {ref,"cb02a2039a9b29dd2eef0446039c9c6e164df9ef"}}, - 0}, + {<<"erlware_commons">>,{pkg,<<"erlware_commons">>,<<"1.3.1">>},1}, {<<"erlydtl">>, {git,"git://github.com/voxoz/erlydtl", {ref,"bdebe6f87d8f989018facbbf8dc6320936ffe98f"}}, 0}, {<<"esockd">>, {git,"https://github.com/voxoz/esockd", - {ref,"a80634b961c315ffe5f020d73236473b53ae5dc9"}}, + {ref,"817a4f059698a349aac9037fc0600b3928036e3d"}}, 0}, {<<"forms">>, {git,"git://github.com/synrc/forms", {ref,"845feb45a46dfc2e0e9a156da9c01c218d8fd6cc"}}, 1}, + {<<"fs">>, + {git,"git://github.com/synrc/fs", + {ref,"45ca2003b208f461ef4acd56c5fdecd2e98e1f33"}}, + 1}, {<<"gen_logger">>, - {git,"git://github.com/voxoz/gen_logger", - {ref,"5b14530363feb0b049c4f5c7c606f815aec781d2"}}, + {git,"git://github.com/NYNJA-MC/gen_logger", + {ref,"edc9b0aa1202cd4129725d3962a8b992fcf0e29c"}}, 1}, {<<"gen_smtp">>, {git,"git://github.com/voxoz/gen_smtp", @@ -76,10 +86,23 @@ {git,"https://github.com/uwiger/gproc", {ref,"1d16f5e6d7cf616eec4395f2385e3a680a4ffc9f"}}, 0}, + {<<"hackney">>, + {git,"https://github.com/benoitc/hackney", + {ref,"f2ac65700ef7918eb4e827892f1a7bb01e826026"}}, + 1}, {<<"ibrowse">>, {git,"git://github.com/cmullaparthi/ibrowse.git", {ref,"c97136cfb61fcc6f39d4e7da47372a64f7fca04e"}}, 1}, + {<<"idna">>,{pkg,<<"idna">>,<<"6.0.0">>},2}, + {<<"jiffy">>, + {git,"https://github.com/davisp/jiffy", + {ref,"c942525130ff0271bd318715406f234c0dc9bc5a"}}, + 1}, + {<<"jose">>, + {git,"https://github.com/NYNJA-MC/jose-erlang.git", + {ref,"709401825367c029986a3bc688de3ed016b914cd"}}, + 0}, {<<"jsx">>, {git,"https://github.com/talentdeficit/jsx.git", {ref,"fc2a001073f2300ba38427c23e83d5673c020542"}}, @@ -94,49 +117,59 @@ 1}, {<<"lager">>, {git,"git://github.com/voxoz/lager", - {ref,"1ecf4c17e9e39dd7a1943140477f632d17518f0c"}}, + {ref,"9d657ab5acc9e82354f945ef819dab3760fe63cb"}}, 1}, {<<"libphonenumber_erlang">>, {git,"https://github.com/marinakr/libphonenumber_erlang.git", - {ref,"3a6be75ef4f6fdd40fa8afee2a7fc5cf2ddb8601"}}, + {ref,"a78037e6f06f0aeeed9eaf4b8abaf378d24a0d01"}}, 0}, {<<"locus">>, {git,"https://github.com/g-andrade/locus.git", {ref,"0ea9079ce5686573e0e70e2b1311343dd25feef8"}}, 0}, + {<<"mad">>, + {git,"git://github.com/synrc/mad", + {ref,"0cd4d9e709d0ca70ec6d01b9d434692eff51222d"}}, + 1}, + {<<"metrics">>,{pkg,<<"metrics">>,<<"1.0.1">>},2}, {<<"migresia">>, {git,"https://github.com/yoonka/migresia.git", {ref,"40e11825e01502d045e87bf8b5d7dc5a9d6e3f73"}}, 0}, + {<<"mimerl">>,{pkg,<<"mimerl">>,<<"1.0.2">>},2}, {<<"mini_s3">>, {git,"https://github.com/chef/mini_s3.git", - {ref,"df0c68ea901343b8e0c647142d88d7f3aae27e7b"}}, + {ref,"73c1be787dfe590113419091c531564e58592478"}}, 0}, {<<"mochiweb">>, {git,"git://github.com/voxoz/mochiweb", {ref,"c53540557dba6d79c347209fc9980e97dcf3dd3f"}}, 1}, {<<"n2o">>, - {git,"git://github.com/synrc/n2o", + {git,"git://github.com/NYNJA-MC/n2o", {ref,"d3a1b52577d701d86c1a4ba4258f3647f3148bd8"}}, - 0}, {<<"nitro">>, {git,"git://github.com/synrc/nitro", {ref,"1aeb421c332f94b135563f8e855b139c1b067f59"}}, 1}, - {<<"opencensus-erlang">>, - {git,"https://github.com/voxoz/opencensus-erlang", - {ref,"8dc9ae86f5c1ef69593fe37b49e649afed6bd201"}}, + {<<"opencensus">>, + {git,"https://github.com/census-instrumentation/opencensus-erlang", + {ref,"7fb276ff73d677c00458922c9180df634f45e018"}}, 0}, {<<"parse_trans">>,{pkg,<<"parse_trans">>,<<"3.3.0">>},2}, {<<"prometheus">>, {git,"https://github.com/deadtrickster/prometheus.erl", - {ref,"46ea4b487baf4f6cc44495eae07582808e0369d4"}}, + {ref,"39c6595728041fa075561c03f3d45881065cc7e7"}}, 0}, + {<<"qdate">>, + {git,"https://github.com/enterprizing/qdate.git", + {ref,"fba988fc54214bb37a3ce11d5c5a3cc68752c3ce"}}, + 0}, + {<<"qdate_localtime">>,{pkg,<<"qdate_localtime">>,<<"1.1.0">>},1}, {<<"ranch">>, - {git,"git://github.com/voxoz/ranch", - {ref,"1a75038c82ede22efec46a7dca192b8ce26309e0"}}, + {git,"https://github.com/ninenines/ranch", + {ref,"3190aef88aea04d6dce8545fe9b4574288903f44"}}, 1}, {<<"rest">>, {git,"git://github.com/synrc/rest", @@ -146,20 +179,36 @@ {git,"https://github.com/talentdeficit/rfc3339", {ref,"90effc078c5e673d025b2c1269a153ad4748d3df"}}, 0}, + {<<"sh">>, + {git,"git://github.com/synrc/sh", + {ref,"a4e646aba9a4c18a34d19089e8b9391076b2bff8"}}, + 2}, {<<"ssl_verify_fun">>,{pkg,<<"ssl_verify_fun">>,<<"1.1.4">>},1}, {<<"stacktrace_compat">>,{pkg,<<"stacktrace_compat">>,<<"1.0.2">>},1}, {<<"syn">>, {git,"git://github.com/ostinelli/syn", {ref,"9964eb8969b6e1e712249d3aed4f3dfafd3aaac3"}}, 1}, + {<<"unicode_util_compat">>,{pkg,<<"unicode_util_compat">>,<<"0.4.1">>},3}, + {<<"uuid">>, + {git,"https://github.com/avtobiff/erlang-uuid.git", + {ref,"cb02a2039a9b29dd2eef0446039c9c6e164df9ef"}}, + 0}, {<<"wts">>, {git,"https://github.com/tsloughter/wts.git", - {ref,"5613b6c4354867fd2b02fde5ef15bf80190b8586"}}, + {ref,"09c039e4d9f7ab9b984c9ffb45dd0db0add58f96"}}, 0}]}. [ {pkg_hash,[ {<<"certifi">>, <<"75424FF0F3BAACCFD34B1214184B6EF616D89E420B258BB0A5EA7D7BC628F7F0">>}, + {<<"cf">>, <<"5CB902239476E141EA70A740340233782D363A31EEA8AD37049561542E6CD641">>}, + {<<"erlware_commons">>, <<"0CE192AD69BC6FD0880246D852D0ECE17631E234878011D1586E053641ED4C04">>}, + {<<"idna">>, <<"689C46CBCDF3524C44D5F3DDE8001F364CD7608A99556D8FBD8239A5798D4C10">>}, + {<<"metrics">>, <<"25F094DEA2CDA98213CECC3AEFF09E940299D950904393B2A29D191C346A8486">>}, + {<<"mimerl">>, <<"993F9B0E084083405ED8252B99460C4F0563E41729AB42D9074FD5E52439BE88">>}, {<<"parse_trans">>, <<"09765507A3C7590A784615CFD421D101AEC25098D50B89D7AA1D66646BC571C1">>}, + {<<"qdate_localtime">>, <<"5F6C3ACF10ECC5A7E2EFA3DCD2C863102B962188DBD9E086EC01D29FE029DA29">>}, {<<"ssl_verify_fun">>, <<"F0EAFFF810D2041E93F915EF59899C923F4568F4585904D010387ED74988E77B">>}, - {<<"stacktrace_compat">>, <<"8AD31C32C9A0EADB1EB298F04DC8B0C8D79BCC6233A638B02791FFCA4F331275">>}]} + {<<"stacktrace_compat">>, <<"8AD31C32C9A0EADB1EB298F04DC8B0C8D79BCC6233A638B02791FFCA4F331275">>}, + {<<"unicode_util_compat">>, <<"D869E4C68901DD9531385BB0C8C40444EBF624E60B6962D95952775CAC5E90CD">>}]} ]. diff --git a/rebar3 b/rebar3 new file mode 100755 index 0000000000000000000000000000000000000000..865ec59000c95fec9d6adf6c0e24f42c88f5014c Binary files /dev/null and b/rebar3 differ diff --git a/spec/nynja_rest.yaml b/spec/nynja_rest.yaml new file mode 100644 index 0000000000000000000000000000000000000000..f53223966be7844693a1ceca1723056193ee59c5 --- /dev/null +++ b/spec/nynja_rest.yaml @@ -0,0 +1,783 @@ +openapi: 3.0.2 +info: + title: Nynja server + description: "REST API for Nynja server" + version: 1.0.0 + +servers: + - url: http://localhost:8888/ + description: "rest api on localhost" + +# [ {"/assets/[...]", cowboy_static, {dir, static_docroot()}} +# , {"/chat/:roster_uuid/message", rest_cowboy_chat_handler, basic_state(chat)} +# , {"/push/message", rest_cowboy_push_handler, basic_state(push_message)} +# , {"/room", rest_cowboy_room_handler, basic_state(room)} +# , {"/publish", rest_cowboy_publish_handler, basic_state(publish)} +# , {"/users", rest_cowboy_users_handler, basic_state(users)} +# , {"/cri/rooms", rest_cowboy_cri_handler, basic_state(cri_room)} +# , {"/cri/rooms/members", rest_cowboy_cri_handler, basic_state(cri_room_members)} +# , {"/cri/bubbles", rest_cowboy_cri_handler, +# basic_state(cri_bubble)} ---> seen in JR's phone to be used!! +# from the client +# , {"/link/:link_id/room_id", rest_cowboy_link_handler, basic_state(link_room)} +# , {"/api/v1/groups/:room_id/messages/csv", +# rest_cowboy_csv_handler, token_state(groups_csv)} +# , {"/api/v1/chats/:phone_id/messages/csv", +# rest_cowboy_csv_handler, token_state(chats_csv)} +# , {"/api/v1/groups/:room_id/messages/:message_id/transcribe", +# rest_cowboy_transcribe_handler, token_state(groups_transcribe)} +# , {"/api/v1/chats/:phone_id/messages/:message_id/transcribe", +# rest_cowboy_transcribe_handler, token_state(chats_transcribe)} +# ]. + +paths: + /test: + get: + operationId: 'IsAlive' + responses: + '200': + description: 'No Content' + content: + application/json: + schema: + type: object + properties: + "Current Time": + type: string + format: date-time + + /sessions: + get: + operationId: 'GetSessions' + parameters: + - in: query + name: phone + schema: + type: string + responses: + '200': + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Session' + '400': + description: "Not found" + content: + text/plain: + '401': + description: "Unauhorized" + content: + text/plain: + '404': + description: "Not found if get_session_api feature is turned off" + content: + text/plain: + + /metrics: + get: + operationId: 'Metrics' + responses: + '200': + content: + text/plain: + + + /admin_whitelist: + get: + operationId: 'GetWhiteList' + parameters: + - in: query + name: phone + schema: + type: string + responses: + '200': + content: + application/html: + application/json: + schema: + type: string + '404': + description: "Not found if whitelist feature is turned off" + + /whitelist: + get: + operationId: 'whitelist' + responses: + '200': + content: + application/json: + schema: + type: object + properties: + Status: + type: string + Data: + type: array + items: + $ref: '#/components/schemas/PhoneEntry' + '404': + description: "Not found if whitelist feature is turned off" + content: + text/plain: + '401': + description: "Unauhorized" + content: + text/plain: + + put: + operationId: 'put_whitelist' + requestBody: + schema: + type: array + items: + type: object + properties: + phone: + type: integer + responses: + '200': + content: + application/json: + schema: + type: object + delete: + operationId: 'delete_whitelist' + parameters: + - in: query + name: phone + schema: + type: string + responses: + '200': + content: + application/json: + schema: + type: object + + + /fake_numbers: + description: + 'Web interface to fake numbers to JSON interface is under /fn' + get: + operationId: 'GetFakeNumbers' + responses: + '200': + content: + text/html: + + /fn: + description: + 'JSON interface to fake numbers' + get: + operationId: 'fake_numbers' + responses: + '200': + content: + application/json: + schema: + type: object + properties: + Status: + type: string + Data: + type: array + items: + $ref: '#/components/schemas/PhoneEntry' + '400': + description: "Not found" + content: + text/plain: + '401': + description: "Unauhorized" + content: + text/plain: + + put: + operationId: 'put_fake_numbers' + requestBody: + schema: + type: array + items: + type: object + properties: + phone: + type: integer + responses: + '200': + content: + application/json: + schema: + type: object + delete: + operationId: 'delete_fake_numbers' + parameters: + - in: query + name: phone + schema: + type: string + responses: + '200': + content: + application/json: + schema: + type: object + + /chat/{roster_uuid}/message: + get: + operationId: 'PostChatMessage' + description: 'Add chat message to a Room' + parameters: + - in: path + name: roster_uuid + schema: + type: string + # required: true + - in: query + name: sender + schema: + type: string + - in: query + name: sender_type + schema: + type: string + - in: query + name: text + schema: + type: string + responses: + '200': + content: + application/json: + schema: + type: object + properties: + status: + type: string + data: + type: string + '400': + description: "Not found" + # content: + # text/plain: + content: + application/json: + schema: + type: object + properties: + status: + type: string + data: + type: string + + '401': + description: "Unauhorized" + content: + application/json: + + + /cri/rooms: + post: + operationId: 'PostCRIRoom' + description: 'JSON interface to rooms' + parameters: + - in: header + name: phoneid + schema: + type: string + # required: true + - in: query + name: room_id + schema: + type: string + # required: true + responses: + '200': + content: + application/json: + schema: + type: object + properties: + status: + type: integer + # typically 200 in this case + data: + type: object + properties: + type: + type: string + + '400': + description: "Not found" + content: + text/plain: + content: + application/json: + schema: + type: object + properties: + status: + type: string + data: + type: string + + '401': + description: "Unauhorized" + content: + text/plain: + delete: + operationId: 'DeleteCRIRoom' + description: 'JSON interface to rooms' + parameters: + - in: header + name: phoneid + schema: + type: string + # required: true + - in: query + name: room_id + schema: + type: string + # required: true + responses: + '200': + content: + application/json: + schema: + type: object + properties: + status: + type: integer + # typically 200 in this case + data: + type: object + properties: + type: + type: string + + '400': + description: "Not found" + content: + text/plain: + content: + application/json: + schema: + type: object + properties: + status: + type: string + data: + type: string + + '401': + description: "Unauhorized" + content: + text/plain: + + /cri/rooms/type: + get: + operationId: 'GetCRIRoom' + description: 'JSON interface to rooms' + parameters: + - in: header + name: phoneid + schema: + type: string + # required: true + - in: query + name: room_id + schema: + type: string + # required: true + responses: + '200': + content: + application/json: + schema: + type: object + properties: + status: + type: integer + # typically 200 in this case + data: + type: object + properties: + type: + type: string + + '400': + description: "Not found" + content: + text/plain: + content: + application/json: + schema: + type: object + properties: + status: + type: string + data: + type: string + + '401': + description: "Unauhorized" + content: + text/plain: + + /cri/rooms/members: + get: + operationId: 'GetMembersCRIRoom' + description: 'JSON interface to rooms' + parameters: + - in: header + name: phoneid + schema: + type: string + # required: true + - in: query + name: room_id + schema: + type: string + # required: true + - in: query + name: phone_ids + schema: + type: string + description: 'comma separated phone ids' + +# requestBody: +# schema: +# type: object +# properties: +# room_id: +# type: string +# join: +# type: boolean +# phone_ids: +# type: array + responses: + '200': + content: + application/json: + schema: + type: object + properties: + status: + type: integer + data: + type: array + items: + $ref: '#/components/schemas/MemberEntry' + + '400': + description: "Not found" + content: + text/plain: + content: + application/json: + schema: + type: object + properties: + status: + type: string + data: + type: string + + '401': + description: "Unauhorized" + content: + text/plain: + + delete: + operationId: 'DeleteMembersCRIRoom' + description: 'JSON interface to rooms' + parameters: + - in: header + name: phoneid + schema: + type: string + # required: true + - in: query + name: room_id + schema: + type: string + # required: true + - in: query + name: phone_ids + schema: + type: string + description: 'comma separated phone ids' + +# requestBody: +# schema: +# type: object +# properties: +# room_id: +# type: string +# join: +# type: boolean +# phone_ids: +# type: array + responses: + '200': + content: + application/json: + schema: + type: object + properties: + status: + type: integer + data: + type: array + items: + $ref: '#/components/schemas/MemberEntry' + + '400': + description: "Not found" + content: + text/plain: + content: + application/json: + schema: + type: object + properties: + status: + type: string + data: + type: string + + '401': + description: "Unauhorized" + content: + text/plain: + '403': + description: "Permission denied" + content: + text/plain: + + /api/v1/groups/{room_id}/messages/csv: + get: + operationId: 'GroupCSV' + description: 'get csv for all messages in room' + parameters: + - in: path + name: room_id + schema: + type: string + required: true + - in: query + name: from + schema: + type: string + default: 0 + - in: query + name: to + description: 'defaults to 20 November 2286' + schema: + type: string + default: 9999999999999 + - in: query + name: saveAsLink + schema: + type: string + - in: query + name: timezone + schema: + type: string + default: "GMT" + - in: query + description: 'Each message type has its own query parameter???' + name: text + schema: + type: boolean + - in: query + name: audio + schema: + type: boolean + - in: query + name: contact + schema: + type: boolean + - in: query + name: location + schema: + type: boolean + - in: query + name: image + schema: + type: boolean + - in: query + name: file + schema: + type: boolean + - in: query + name: video + schema: + type: boolean + - in: query + name: link + schema: + type: boolean + + responses: + '200': + headers: + content-disposition: + content-length: + content: + text/csv: + + '400': + description: "Not found" + content: + text/plain: + content: + application/json: + schema: + type: object + properties: + status: + type: string + data: + type: string + '401': + description: "Unauhorized" + content: + text/plain: + '404': + description: "Not Found" + content: + application/json: + + /api/v1/chats/{phone_id}/messages/csv: + get: + operationId: 'ChatsCSV' + description: 'Get csv of all chats to user with phone_id (Authentication gives from is)' + parameters: + - in: path + name: phone_id + schema: + type: string + required: true + operationId: 'GroupCSV' + - in: query + name: from + schema: + type: string + default: 0 + - in: query + name: to + description: 'defaults to 20 November 2286' + schema: + type: string + default: 9999999999999 + - in: query + name: saveAsLink + schema: + type: string + - in: query + name: timezone + schema: + type: string + default: "GMT" + - in: query + description: 'Each message type has its own query parameter???' + name: text + schema: + type: boolean + - in: query + name: audio + schema: + type: boolean + - in: query + name: contact + schema: + type: boolean + - in: query + name: location + schema: + type: boolean + - in: query + name: image + schema: + type: boolean + - in: query + name: file + schema: + type: boolean + - in: query + name: video + schema: + type: boolean + - in: query + name: link + schema: + type: boolean + + responses: + '200': + headers: + content-disposition: + content-length: + content: + text/csv: + + '400': + description: "Not found" + content: + text/plain: + content: + application/json: + schema: + type: object + properties: + status: + type: string + data: + type: string + '401': + description: "Unauhorized" + content: + text/plain: + '404': + description: "Not Found" + content: + application/json: + + +components: + schemas: + + Session: + type: object + properties: + created: + type: integer + + PhoneEntry: + type: object + properties: + created: + type: string + formst: date-time + phone: + type: integer + + MemberEntry: + type: object + properties: + is_member: + type: string + ## should be boolean, but jsx_tuples_to_json broken + phone_id: + type: string + error: + type: string + required: + - is_member + - phone_id + + RoomId: + type: object diff --git a/sys.config b/sys.config index 74771f3c109a7ee9da41beab6a443ded50ff2c9e..4cc69d0e2c9f4b524b8c5e1708f71c5295a8d812 100644 --- a/sys.config +++ b/sys.config @@ -1,43 +1,64 @@ +%% -*- mode:erlang; erlang-indent-level: 2; indent-tabs-mode: nil -*- [ - {review,[{host,"ns.synrc.com"}]}, - {bert,[{js,"apps/roster/priv/macbert/"}, - {erl,"apps/roster/src"}, - {google,"apps/service/priv/"}, - {disallowed,['feed', 'Whitelist', 'FakeNumbers','Schedule','Index','process']}, - {allowed_hrl, ["roster"]}, - {custom_validate, {roster, validate}}, - {swift,"apps/roster/priv/macbert/"}]}, - {roster,[ - {freeze_time, 1000}, - {upload,"./storage"}, - {email_api,[ - {from,<<"Nynja App">>}, - {host,<<"smtp.gmail.com">>}, - {login,<<"sandbox.nynja.app@gmail.com">>}, - {password,<<"tecSynt_nynja110917">>}]}, - {vox_api,[ - {account_id,<<"1152724">>}, - {api_key,<<"94b9f0f0-7d93-4e63-9061-aef62c30182b">>}, - {application_name,<<"videoconf.nynja.voximplant.com">>}, - {conference_rule_id,<<"294893">>}]}, - {telesign_api,[ - {customer_id,<<"CF6E2918-89A9-417F-8074-382908FEDCD9">>}, - {api_key,<<"JE1zC3gjYg1fITkPK4xYtAGk0uVpbsWSqi7e54wg+sLbMGAbKoJSw0/BRJaAezZpVZzEQfHYyL3PKutGfZ38Gg==">>}]}, - {amazon_api,[ - {access_key_id,<<"AKIAITNYSJXI2NUDRRXA">>}, - {secret_access_key,<<"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">>}, - {sts, [ - {host, <<"sts.amazonaws.com">>}, - {region, <<"us-east-1">>}, - {service, <<"sts">>}, - {action, <<"GetSessionToken">>}, - {duration_seconds, <<"43200">>}, - {version, <<"2011-06-15">>}, - {http_method, <<"GET">>}, - {endpoint, <<"https://sts.amazonaws.com">>} - ]}, - {s3, [ - {default_bucket_name,<<"nynja-defaults">>} + {kernel, + [{logger_level, info}, + {logger, + [ {handler, default, logger_std_h, + #{level => error, + config => #{file => "log/erlang.log"}, + formatter => {logger_formatter, + #{template => [time," [",level,"] ",pid," [", module, "] ", msg,"\n"]}} + }}, + {handler, info_handler, logger_std_h, + #{level => info, + config => #{file => "log/info.log"}, + formatter => {logger_formatter, + #{template => [time," [",level,"] ",pid," [", module, "] ", msg,"\n"]}} + }}, + {handler, debug_handler, logger_std_h, + #{level => debug, + config => #{file => "log/debug.log"}, + formatter => {logger_formatter, + #{template => [time," [",level,"] ",pid," [", module, "] ", msg,"\n"]}} + }} + ]}]}, + {review,[{host,"ns.synrc.com"}]}, + {roster, + [ + {freeze_time, 1000}, + {upload,"./storage"}, + {email_api, + [ + {from,<<"Nynja App">>}, + {host,<<"smtp.gmail.com">>}, + {login,<<"sandbox.nynja.app@gmail.com">>}, + {password,<<"tecSynt_nynja110917">>}]}, + {vox_api, + [ + {account_id,<<"1152724">>}, + {api_key,<<"94b9f0f0-7d93-4e63-9061-aef62c30182b">>}, + {application_name,<<"videoconf.nynja.voximplant.com">>}, + {conference_rule_id,<<"294893">>}]}, + {telesign_api, + [ + {customer_id,<<"CF6E2918-89A9-417F-8074-382908FEDCD9">>}, + {api_key,<<"JE1zC3gjYg1fITkPK4xYtAGk0uVpbsWSqi7e54wg+sLbMGAbKoJSw0/BRJaAezZpVZzEQfHYyL3PKutGfZ38Gg==">>}]}, + {amazon_api, + [ + {access_key_id,<<"AKIAITNYSJXI2NUDRRXA">>}, + {secret_access_key,<<"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">>}, + {sts, [ + {host, <<"sts.amazonaws.com">>}, + {region, <<"us-east-1">>}, + {service, <<"sts">>}, + {action, <<"GetSessionToken">>}, + {duration_seconds, <<"43200">>}, + {version, <<"2011-06-15">>}, + {http_method, <<"GET">>}, + {endpoint, <<"https://sts.amazonaws.com">>} + ]}, + {s3, [ + {default_bucket_name,<<"nynja-defaults">>} ]} ]}, {google_api, [ @@ -57,118 +78,125 @@ {fake_numbers_check, true}, {validate_token, micro_auth} ]}, - {rest, [{port,8888}, - {basic_auth,[ - {username,<<"nynja">>}, - {password,<<"nynjaTS">>}]}, -%% api which can be opened without basic auth - {open_api_list, [<<"/test">>, <<"/metrics">>]} - ]}, - {n2o, [{port,8000}, - {upload,"./storage"}, - {app,review}, - {tables,[ cookies, file, caching, ring, async, nick, names, surnames, system]}, - {pickler,nitro_pickle}, - {formatter,n2o_bert}, - {auth_ttl, 86400}, %% 24 h - {protocols,[n2o_nitro,n2o_ftp,roster_proto]}, - {log_modules,[roster, n2o_vnode]}, - {log_level,roster}, - {vnode, {roster, get_vnode}}, - {validate, {roster, validate}} + {rest, [{port, 8888}, + {start_cowboy, true}, + {basic_auth,[ + {username,<<"nynja">>}, + {password,<<"nynjaTS">>}]}, + %% api which can be opened without basic auth + {open_api_list, [<<"/test">>, <<"/metrics">>]} ]}, - {emq_dashboard, [ - {listeners_dash,[ - {http,18083,[ - {acceptors,4},{max_clients,512}] - }, - {https,18084,[ - {acceptors,4},{max_clients,512},{handshake_timeout, 15}, -% {certfile, "etc/certs/cert.pem"}, - {keyfile, "etc/certs/privkey.pem"}, - {cacertfile ,"etc/certs/fullchain.pem"}, - {verify,verify_peer}, - {fail_if_no_peer_cert, false}]} + {n2o, [{port,8000}, + {upload,"./storage"}, + {app,review}, + {tables,[ cookies, file, caching, ring, async, nick, names, surnames, system]}, + {pickler,nitro_pickle}, + {formatter,n2o_bert}, + {auth_ttl, 86400}, %% 24 h + {protocols,[n2o_nitro,n2o_ftp,roster_proto]}, + {log_modules,[roster, n2o_vnode]}, + {log_level,roster}, + {vnode, {roster, get_vnode}}, + {validate, {roster, validate}} ]}, - {default_admin, [ - {name, <<"nynja">>}, - {password, <<"nynjaAdmin">>} - ]}]}, - {emq_modules, [{modules,[{emq_mod_presence,[{qos,1}]}, - {emq_mod_subscription,[{<<"%u/%c/#">>,2}]}, - {emq_mod_rewrite,[{rewrite,"x/#","^x/y/(.+)$","z/y/$1"}, - {rewrite,"y/+/z/#","^y/(.+)/z/(.+)$","y/z/$2"}]}]}]}, -{emqttd, - [{plugins_loaded_file,"etc/loaded_plugins"}, - {plugins_etc_dir,"etc/plugins/"}, - {broker_sys_interval,60}, - {conn_force_gc_count,100}, - {cache_acl,true}, - {acl_file,"etc/acl.conf"}, - {allow_anonymous,true}, - {protocol,[{max_clientid_len,1024},{max_packet_size,25165824}]}, - {client,[{client_idle_timeout,30000},{client_enable_stats,false}]}, - {session, - [{upgrade_qos,false}, - {max_inflight,32}, - {retry_interval,20000}, - {max_awaiting_rel,100000}, - {await_rel_timeout,60000}, - {enable_stats,false}, - {expiry_interval,7200000}]}, - {queue, - [{priority,[]}, - {type,simple}, - {max_length,infinity}, - {low_watermark,0.2}, - {high_watermark,0.6}, - {queue_qos0,true}]}, - {pubsub,[{pool_size,8},{by_clientid,true},{async,true}]}, - {bridge,[{max_queue_len,10000},{ping_down_interval,1}]}, - {listeners, - [{tcp,1883, - [{connopts,[]}, - {sockopts,[{backlog,1024},{nodelay,true}]}, - {acceptors,8}, - {max_clients,1048576}, - {tune_buffer,false}]}, - {ssl,8883, - [{sslopts, - [{versions,['tlsv1.2','tlsv1.1',tlsv1]}, - {cacertfile,"etc/certs/cert.pem"}, - {keyfile,"etc/certs/privkey.pem"}, - {certfile,"etc/certs/fullchain.pem"}]}, - {connopts,[]}, - {sockopts,[{nodelay,true}]}, - {acceptors,8}, - {max_clients,1048576}]}, - {http,8083, - [{connopts,[]}, - {sockopts,[{nodelay,true}]}, - {acceptors,8}, - {max_clients,1048576}]}, - {https,8084, - [{sslopts, - [{cacertfile,"etc/certs/cert.pem"}, - {keyfile,"etc/certs/privkey.pem"}, - {certfile,"etc/certs/fullchain.pem"}]}, - {connopts,[]}, - {sockopts,[{nodelay,true}]}, - {acceptors,8}, - {max_clients,1048576}]}]}, - {sysmon, - [{long_gc,false}, - {long_schedule,2400}, - {large_heap,67108864}, - {busy_port,false}, - {busy_dist_port,true}]}]}, - {kvs, [{dba,store_mnesia}, + {emq_dashboard, + [ + {listeners_dash, + [ + {http,18083, + [ + {acceptors,4},{max_clients,512}] + }, + {https,18084, + [ + {acceptors,4},{max_clients,512},{handshake_timeout, 15}, + %% {certfile, "etc/certs/cert.pem"}, + {keyfile, "etc/certs/privkey.pem"}, + {cacertfile ,"etc/certs/fullchain.pem"}, + {verify,verify_peer}, + {fail_if_no_peer_cert, false}]} + ]}, + {default_admin, [ + {name, <<"nynja">>}, + {password, <<"nynjaAdmin">>} + ]}]}, + {emq_modules, + [{modules,[{emq_mod_presence,[{qos,1}]}, + {emq_mod_subscription,[{<<"%u/%c/#">>,2}]}, + {emq_mod_rewrite, + [{rewrite,"x/#","^x/y/(.+)$","z/y/$1"}, + {rewrite,"y/+/z/#","^y/(.+)/z/(.+)$","y/z/$2"}]}]}]}, + {emqttd, + [{plugins_loaded_file,"etc/loaded_plugins"}, + {plugins_etc_dir,"etc/plugins/"}, + {broker_sys_interval,60}, + {conn_force_gc_count,100}, + {cache_acl,true}, + {acl_file,"etc/acl.conf"}, + {allow_anonymous,true}, + {protocol,[{max_clientid_len,1024},{max_packet_size,25165824}]}, + {client,[{client_idle_timeout,30000},{client_enable_stats,false}]}, + {session, + [{upgrade_qos,false}, + {max_inflight,32}, + {retry_interval,20000}, + {max_awaiting_rel,100000}, + {await_rel_timeout,60000}, + {enable_stats,false}, + {expiry_interval,7200000}]}, + {queue, + [{priority,[]}, + {type,simple}, + {max_length,infinity}, + {low_watermark,0.2}, + {high_watermark,0.6}, + {queue_qos0,true}]}, + {pubsub,[{pool_size,8},{by_clientid,true},{async,true}]}, + {bridge,[{max_queue_len,10000},{ping_down_interval,1}]}, + {listeners, + [{tcp,1883, + [{connopts,[]}, + {sockopts,[{backlog,1024},{nodelay,true}]}, + {acceptors,8}, + {max_clients,1048576}, + {tune_buffer,false}]}, + {ssl,8883, + [{sslopts, + [{versions,['tlsv1.2','tlsv1.1',tlsv1]}, + {cacertfile,"etc/certs/cert.pem"}, + {keyfile,"etc/certs/privkey.pem"}, + {certfile,"etc/certs/fullchain.pem"}]}, + {connopts,[]}, + {sockopts,[{nodelay,true}]}, + {acceptors,8}, + {max_clients,1048576}]}, + {http,8083, + [{connopts,[]}, + {sockopts,[{nodelay,true}]}, + {acceptors,8}, + {max_clients,1048576}]}, + {https,8084, + [{sslopts, + [{cacertfile,"etc/certs/cert.pem"}, + {keyfile,"etc/certs/privkey.pem"}, + {certfile,"etc/certs/fullchain.pem"}]}, + {connopts,[]}, + {sockopts,[{nodelay,true}]}, + {acceptors,8}, + {max_clients,1048576}]}]}, + {sysmon, + [{long_gc,false}, + {long_schedule,2400}, + {large_heap,67108864}, + {busy_port,false}, + {busy_dist_port,true}]}]}, + {kvs, [{dba,store_mnesia}, {schema, [kvs_user, kvs_acl, kvs_feed, kvs_subscription, roster, micro, emqttd_kvs, bpe_metainfo]} -%% ,{generation, {roster_test, limit}} -%% ,{forbidding, {roster_test, forbid}} - ]}, - {bpe,[{process_worker,{job,worker}} - ,{ttl, 8640000} - ]}, - {locus, [{url, "http://geolite.maxmind.com/download/geoip/database/GeoLite2-City.tar.gz"}]} + %% ,{generation, {roster_test, limit}} + %% ,{forbidding, {roster_test, forbid}} + ]}, + {bpe,[{process_worker,{job,worker}} + ,{ttl, 8640000} + ]}, + {locus, [{url, "http://geolite.maxmind.com/download/geoip/database/GeoLite2-City.tar.gz"}]} ]. diff --git a/test/api_nynja.erl b/test/api_nynja.erl new file mode 100644 index 0000000000000000000000000000000000000000..2be7a3b1c363d26b83c6d6247a0d25a968999395 --- /dev/null +++ b/test/api_nynja.erl @@ -0,0 +1,833 @@ +%% coding: latin-1 +%% This code is generated from ../server/spec/nynja_rest.yaml +%% on 2020-04-21 10:59:15 UTC +%% Using openapi rebar3 plugin version: #2643e8e +%% Do not manually change this code! +%% +%% json_schema/0 implements a JSON Schema for the definitions +%% Reference should be fixed! +%% At the moment, ref with definition is not folowwed by jesse +%% Use jsx:prettify(jsx:encode(json_schema())) to get a JSON string. + +-module(api_nynja). + +-export([json_schema/0]). +-export([operation/1, operations/0, definitions/0, baseurl/1]). + +-export([request/3, http_request/4]). +-export([validate_request/2, validate_response/4]). + +baseurl(Config) -> + proplists:get_value(base_url, Config, <<"http://localhost:8888/">>). + +definitions() -> + [{"/components/schemas/RoomId",#{<<"type">> => <<"object">>}}, + {"/components/schemas/MemberEntry", + #{<<"properties">> => + #{<<"error">> => #{<<"type">> => <<"string">>}, + <<"is_member">> => #{<<"type">> => <<"string">>}, + <<"phone_id">> => #{<<"type">> => <<"string">>}}, + <<"required">> => [<<"is_member">>,<<"phone_id">>], + <<"type">> => <<"object">>}}, + {"/components/schemas/PhoneEntry", + #{<<"properties">> => + #{<<"created">> => + #{<<"formst">> => <<"date-time">>,<<"type">> => <<"string">>}, + <<"phone">> => #{<<"type">> => <<"integer">>}}, + <<"type">> => <<"object">>}}, + {"/components/schemas/Session", + #{<<"properties">> => #{<<"created">> => #{<<"type">> => <<"integer">>}}, + <<"type">> => <<"object">>}}]. + +operations() -> + #{'ChatsCSV' => + #{method => get, + parameters => + [[{"in","path"}, + {"name","phone_id"}, + {"schema", + #{<<"operationId">> => <<"GroupCSV">>,<<"required">> => true, + <<"type">> => <<"string">>}}], + [{"in","query"}, + {"name","from"}, + {"default",0}, + {"schema",#{<<"type">> => <<"string">>}}], + [{"in","query"}, + {"name","to"}, + {"description","defaults to 20 November 2286"}, + {"default",9999999999999}, + {"schema",#{<<"type">> => <<"string">>}}], + [{"in","query"}, + {"name","saveAsLink"}, + {"schema",#{<<"type">> => <<"string">>}}], + [{"in","query"}, + {"name","timezone"}, + {"default","GMT"}, + {"schema",#{<<"type">> => <<"string">>}}], + [{"in","query"}, + {"description", + "Each message type has its own query parameter???"}, + {"name","text"}, + {"schema",#{<<"type">> => <<"boolean">>}}], + [{"in","query"}, + {"name","audio"}, + {"schema",#{<<"type">> => <<"boolean">>}}], + [{"in","query"}, + {"name","contact"}, + {"schema",#{<<"type">> => <<"boolean">>}}], + [{"in","query"}, + {"name","location"}, + {"schema",#{<<"type">> => <<"boolean">>}}], + [{"in","query"}, + {"name","image"}, + {"schema",#{<<"type">> => <<"boolean">>}}], + [{"in","query"}, + {"name","file"}, + {"schema",#{<<"type">> => <<"boolean">>}}], + [{"in","query"}, + {"name","video"}, + {"schema",#{<<"type">> => <<"boolean">>}}], + [{"in","query"}, + {"name","link"}, + {"schema",#{<<"type">> => <<"boolean">>}}]], + path => <<"/api/v1/chats/{phone_id}/messages/csv">>, + responses => + #{200 => [{"text/csv",undefined}], + 400 => [{"text/plain",undefined}], + 401 => [{"text/plain",undefined}], + 404 => [{"application/json",undefined}]}, + tags => []}, + 'DeleteCRIRoom' => + #{method => delete, + parameters => + [[{"in","header"}, + {"name","phoneid"}, + {"schema",#{<<"type">> => <<"string">>}}], + [{"in","query"}, + {"name","room_id"}, + {"schema",#{<<"type">> => <<"string">>}}]], + path => <<"/cri/rooms">>, + responses => + #{200 => + [{"application/json", + #{schema => + #{<<"properties">> => + #{<<"data">> => + #{<<"properties">> => + #{<<"type">> => + #{<<"type">> => + <<"string">>}}, + <<"type">> => <<"object">>}, + <<"status">> => + #{<<"type">> => <<"integer">>}}, + <<"type">> => <<"object">>}}}], + 400 => [{"text/plain",undefined}], + 401 => [{"text/plain",undefined}]}, + tags => []}, + 'DeleteMembersCRIRoom' => + #{method => delete, + parameters => + [[{"in","header"}, + {"name","phoneid"}, + {"schema",#{<<"type">> => <<"string">>}}], + [{"in","query"}, + {"name","room_id"}, + {"schema",#{<<"type">> => <<"string">>}}], + [{"in","query"}, + {"name","phone_ids"}, + {"description","comma separated phone ids"}, + {"schema",#{<<"type">> => <<"string">>}}]], + path => <<"/cri/rooms/members">>, + responses => + #{200 => + [{"application/json", + #{schema => + #{<<"properties">> => + #{<<"data">> => + #{<<"items">> => + #{<<"$ref">> => + <<"/components/schemas/MemberEntry">>}, + <<"type">> => <<"array">>}, + <<"status">> => + #{<<"type">> => <<"integer">>}}, + <<"type">> => <<"object">>}}}], + 400 => [{"text/plain",undefined}], + 401 => [{"text/plain",undefined}], + 403 => [{"text/plain",undefined}]}, + tags => []}, + 'GetCRIRoom' => + #{method => get, + parameters => + [[{"in","header"}, + {"name","phoneid"}, + {"schema",#{<<"type">> => <<"string">>}}], + [{"in","query"}, + {"name","room_id"}, + {"schema",#{<<"type">> => <<"string">>}}]], + path => <<"/cri/rooms/type">>, + responses => + #{200 => + [{"application/json", + #{schema => + #{<<"properties">> => + #{<<"data">> => + #{<<"properties">> => + #{<<"type">> => + #{<<"type">> => + <<"string">>}}, + <<"type">> => <<"object">>}, + <<"status">> => + #{<<"type">> => <<"integer">>}}, + <<"type">> => <<"object">>}}}], + 400 => [{"text/plain",undefined}], + 401 => [{"text/plain",undefined}]}, + tags => []}, + 'GetFakeNumbers' => + #{method => get,parameters => [],path => <<"/fake_numbers">>, + responses => #{200 => [{"text/html",undefined}]}, + tags => []}, + 'GetMembersCRIRoom' => + #{method => get, + parameters => + [[{"in","header"}, + {"name","phoneid"}, + {"schema",#{<<"type">> => <<"string">>}}], + [{"in","query"}, + {"name","room_id"}, + {"schema",#{<<"type">> => <<"string">>}}], + [{"in","query"}, + {"name","phone_ids"}, + {"description","comma separated phone ids"}, + {"schema",#{<<"type">> => <<"string">>}}]], + path => <<"/cri/rooms/members">>, + responses => + #{200 => + [{"application/json", + #{schema => + #{<<"properties">> => + #{<<"data">> => + #{<<"items">> => + #{<<"$ref">> => + <<"/components/schemas/MemberEntry">>}, + <<"type">> => <<"array">>}, + <<"status">> => + #{<<"type">> => <<"integer">>}}, + <<"type">> => <<"object">>}}}], + 400 => [{"text/plain",undefined}], + 401 => [{"text/plain",undefined}]}, + tags => []}, + 'GetSessions' => + #{method => get, + parameters => + [[{"in","query"}, + {"name","phone"}, + {"schema",#{<<"type">> => <<"string">>}}]], + path => <<"/sessions">>, + responses => + #{200 => + [{"application/json", + #{schema => + #{<<"items">> => + #{<<"$ref">> => + <<"/components/schemas/Session">>}, + <<"type">> => <<"array">>}}}], + 400 => [{"text/plain",undefined}], + 401 => [{"text/plain",undefined}], + 404 => [{"text/plain",undefined}]}, + tags => []}, + 'GetWhiteList' => + #{method => get, + parameters => + [[{"in","query"}, + {"name","phone"}, + {"schema",#{<<"type">> => <<"string">>}}]], + path => <<"/admin_whitelist">>, + responses => + #{200 => + [{"application/json", + #{schema => #{<<"type">> => <<"string">>}}}, + {"application/html",undefined}], + 404 => []}, + tags => []}, + 'GroupCSV' => + #{method => get, + parameters => + [[{"in","path"}, + {"name","room_id"}, + {"required",true}, + {"schema",#{<<"type">> => <<"string">>}}], + [{"in","query"}, + {"name","from"}, + {"default",0}, + {"schema",#{<<"type">> => <<"string">>}}], + [{"in","query"}, + {"name","to"}, + {"description","defaults to 20 November 2286"}, + {"default",9999999999999}, + {"schema",#{<<"type">> => <<"string">>}}], + [{"in","query"}, + {"name","saveAsLink"}, + {"schema",#{<<"type">> => <<"string">>}}], + [{"in","query"}, + {"name","timezone"}, + {"default","GMT"}, + {"schema",#{<<"type">> => <<"string">>}}], + [{"in","query"}, + {"description", + "Each message type has its own query parameter???"}, + {"name","text"}, + {"schema",#{<<"type">> => <<"boolean">>}}], + [{"in","query"}, + {"name","audio"}, + {"schema",#{<<"type">> => <<"boolean">>}}], + [{"in","query"}, + {"name","contact"}, + {"schema",#{<<"type">> => <<"boolean">>}}], + [{"in","query"}, + {"name","location"}, + {"schema",#{<<"type">> => <<"boolean">>}}], + [{"in","query"}, + {"name","image"}, + {"schema",#{<<"type">> => <<"boolean">>}}], + [{"in","query"}, + {"name","file"}, + {"schema",#{<<"type">> => <<"boolean">>}}], + [{"in","query"}, + {"name","video"}, + {"schema",#{<<"type">> => <<"boolean">>}}], + [{"in","query"}, + {"name","link"}, + {"schema",#{<<"type">> => <<"boolean">>}}]], + path => <<"/api/v1/groups/{room_id}/messages/csv">>, + responses => + #{200 => [{"text/csv",undefined}], + 400 => [{"text/plain",undefined}], + 401 => [{"text/plain",undefined}], + 404 => [{"application/json",undefined}]}, + tags => []}, + 'IsAlive' => + #{method => get,parameters => [],path => <<"/test">>, + responses => + #{200 => + [{"application/json", + #{schema => + #{<<"properties">> => + #{<<"Current Time">> => + #{<<"format">> => <<"date-time">>, + <<"type">> => <<"string">>}}, + <<"type">> => <<"object">>}}}]}, + tags => []}, + 'Metrics' => + #{method => get,parameters => [],path => <<"/metrics">>, + responses => #{200 => [{"text/plain",undefined}]}, + tags => []}, + 'PostCRIRoom' => + #{method => post, + parameters => + [[{"in","header"}, + {"name","phoneid"}, + {"schema",#{<<"type">> => <<"string">>}}], + [{"in","query"}, + {"name","room_id"}, + {"schema",#{<<"type">> => <<"string">>}}]], + path => <<"/cri/rooms">>, + responses => + #{200 => + [{"application/json", + #{schema => + #{<<"properties">> => + #{<<"data">> => + #{<<"properties">> => + #{<<"type">> => + #{<<"type">> => + <<"string">>}}, + <<"type">> => <<"object">>}, + <<"status">> => + #{<<"type">> => <<"integer">>}}, + <<"type">> => <<"object">>}}}], + 400 => [{"text/plain",undefined}], + 401 => [{"text/plain",undefined}]}, + tags => []}, + 'PostChatMessage' => + #{method => get, + parameters => + [[{"in","path"}, + {"name","roster_uuid"}, + {"schema",#{<<"type">> => <<"string">>}}], + [{"in","query"}, + {"name","sender"}, + {"schema",#{<<"type">> => <<"string">>}}], + [{"in","query"}, + {"name","sender_type"}, + {"schema",#{<<"type">> => <<"string">>}}], + [{"in","query"}, + {"name","text"}, + {"schema",#{<<"type">> => <<"string">>}}]], + path => <<"/chat/{roster_uuid}/message">>, + responses => + #{200 => + [{"application/json", + #{schema => + #{<<"properties">> => + #{<<"data">> => #{<<"type">> => <<"string">>}, + <<"status">> => + #{<<"type">> => <<"string">>}}, + <<"type">> => <<"object">>}}}], + 400 => + [{"application/json", + #{schema => + #{<<"properties">> => + #{<<"data">> => #{<<"type">> => <<"string">>}, + <<"status">> => + #{<<"type">> => <<"string">>}}, + <<"type">> => <<"object">>}}}], + 401 => [{"application/json",undefined}]}, + tags => []}, + delete_fake_numbers => + #{method => delete, + parameters => + [[{"in","query"}, + {"name","phone"}, + {"schema",#{<<"type">> => <<"string">>}}]], + path => <<"/fn">>, + responses => + #{200 => + [{"application/json", + #{schema => #{<<"type">> => <<"object">>}}}]}, + tags => []}, + delete_whitelist => + #{method => delete, + parameters => + [[{"in","query"}, + {"name","phone"}, + {"schema",#{<<"type">> => <<"string">>}}]], + path => <<"/whitelist">>, + responses => + #{200 => + [{"application/json", + #{schema => #{<<"type">> => <<"object">>}}}]}, + tags => []}, + fake_numbers => + #{method => get,parameters => [],path => <<"/fn">>, + responses => + #{200 => + [{"application/json", + #{schema => + #{<<"properties">> => + #{<<"Data">> => + #{<<"items">> => + #{<<"$ref">> => + <<"/components/schemas/PhoneEntry">>}, + <<"type">> => <<"array">>}, + <<"Status">> => + #{<<"type">> => <<"string">>}}, + <<"type">> => <<"object">>}}}], + 400 => [{"text/plain",undefined}], + 401 => [{"text/plain",undefined}]}, + tags => []}, + put_fake_numbers => + #{method => put,parameters => [],path => <<"/fn">>, + responses => + #{200 => + [{"application/json", + #{schema => #{<<"type">> => <<"object">>}}}]}, + tags => []}, + put_whitelist => + #{method => put,parameters => [],path => <<"/whitelist">>, + responses => + #{200 => + [{"application/json", + #{schema => #{<<"type">> => <<"object">>}}}]}, + tags => []}, + whitelist => + #{method => get,parameters => [],path => <<"/whitelist">>, + responses => + #{200 => + [{"application/json", + #{schema => + #{<<"properties">> => + #{<<"Data">> => + #{<<"items">> => + #{<<"$ref">> => + <<"/components/schemas/PhoneEntry">>}, + <<"type">> => <<"array">>}, + <<"Status">> => + #{<<"type">> => <<"string">>}}, + <<"type">> => <<"object">>}}}], + 401 => [{"text/plain",undefined}], + 404 => [{"text/plain",undefined}]}, + tags => []}}. + +json_schema() -> + #{<<"$schema">> => + <<"http://json-schema.org/draft-04/schema#">>, + <<"properties">> => + maps:from_list([{iolist_to_binary(K), V} + || {K, V} <- definitions()])}. + +operation(Id) -> maps:get(Id, operations()). + + + +request(OpId, Params, Config) -> + request(api_nynja, OpId, Params, Config). + +validate_request(OperationId, Args) -> + validate_request(api_nynja, OperationId, Args). + +validate_response(OperationId, StatusCode, Headers, + RawBody) -> + validate_response(api_nynja, OperationId, StatusCode, + Headers, RawBody). + +request(Mod, OpId, ParamsIn, Config) -> + begin + Params = if is_list(ParamsIn) -> + maps:from_list(ParamsIn); + true -> ParamsIn + end, + Op = Mod:operation(OpId), + Method = maps:get(method, Op), + BaseUrl = string:trim(Mod:baseurl(Config), trailing, + "/"), + {ParamHeaders, HKeys} = header(Op, Params), + {Path, PKeys} = path(Op, Params), + {Query, QKeys} = query(Op, Params), + Headers = ParamHeaders ++ + proplists:get_value(headers, Config, []) ++ + case proplists:get_value(security, Config) of + undefined -> []; + {authorization_basic, + [#{username := User, password := Password}]} -> + Token = + base64:encode(unicode:characters_to_binary([User, + ":", + Password])), + [{"Authorization", + io_lib:format("Basic ~s", [Token])}]; + {authorization_bearer, [#{accessToken := Token}]} -> + [{"Authorization", + io_lib:format("Bearer ~s", [Token])}] + end, + Keys = HKeys ++ PKeys ++ QKeys, + BodyParams = maps:get(body, Params, + maps:without(Keys, Params)), + case Method of + get -> + #{method => Method, + url => iolist_to_binary([BaseUrl, Path, Query]), + headers => Headers}; + _ -> + {ContentType, Body} = make_body(BodyParams, Path), + #{method => Method, + url => iolist_to_binary([BaseUrl, Path, Query]), + headers => Headers, body => Body, + content_type => ContentType} + end + end. + +header(#{path := Endpoint, parameters := Parameters}, + Args) + when is_map(Args) -> + begin + InHeader = [Param + || Param <- Parameters, + lists:member({"in", "header"}, Param)], + {lists:foldl(fun (Param, Headers) -> + Name = proplists:get_value("name", Param), + case {proplists:get_value("required", Param, false), + get_by_similar_key(Name, Args)} + of + {false, undefined} -> Headers; + {true, undefined} -> + error({required, Name, Param, Endpoint}); + {_, {Key, Value}} -> + [{to_str(Key), to_str(Value)} | Headers] + end + end, + [], InHeader), + InHeader} + end. + +path(#{path := Endpoint, parameters := Parameters}, + Args) + when is_map(Args) -> + begin + InPath = [Param + || Param <- Parameters, + lists:member({"in", "path"}, Param)], + {lists:foldl(fun (Param, Path) -> + Name = proplists:get_value("name", Param), + case {proplists:get_value("required", Param, false), + get_by_similar_key(Name, Args)} + of + {false, undefined} -> Path; + {true, undefined} -> + throw({error, + {required, Name, Param, Endpoint}}); + {_, {_Key, Value}} -> + iolist_to_binary(string:replace(Path, + "{" ++ + Name ++ "}", + to_str(Value))) + end + end, + Endpoint, InPath), + InPath} + end. + +query(#{path := Endpoint, parameters := Parameters}, + Args) + when is_map(Args) -> + begin + InQuery = [Param + || Param <- Parameters, + lists:member({"in", "query"}, Param)], + Query = lists:foldr(fun (Param, Query) -> + Name = proplists:get_value("name", Param), + Explode = proplists:get_value("explode", + Param, false), + case {proplists:get_value("required", Param, + false), + get_by_similar_key(Name, Args, Explode)} + of + {false, undefined} -> Query; + {true, undefined} -> + throw({error, + {required, Name, Param, + Endpoint}}); + {_, {_, Value}} -> + query_serialize(Name, Value, Param) ++ + Query + end + end, + [], InQuery), + {case [[K, "=", V] || {K, V} <- Query] of + [] -> <<>>; + Qs -> iolist_to_binary(["?" | lists:join("&", Qs)]) + end, + InQuery} + end. + +query_serialize(Name, Value, Param) -> + begin + Style = proplists:get_value("style", Param, "form"), + Explode = proplists:get_value("explode", Param, true), + Array = is_array(proplists:get_value("schema", Param)), + AllowReserved = proplists:get_value("allowReserved", + Param, false), + EncodedValue = case AllowReserved of + true -> Value; + false when Array -> + [http_uri:encode(to_str(V)) || V <- Value]; + false -> http_uri:encode(to_str(Value)) + end, + case {Array, Explode} of + {false, _} -> [{Name, EncodedValue}]; + {true, true} -> [{Name, V} || V <- EncodedValue]; + {true, false} -> + case Style of + "form" -> + [{Name, + iolist_to_binary(lists:join(",", EncodedValue))}]; + "spaceDelimited" -> + [{Name, + iolist_to_binary(lists:join("%20", EncodedValue))}]; + "pipeDelimited" -> + [{Name, + iolist_to_binary(lists:join("|", EncodedValue))}] + end + end + end. + +is_array(Schema) -> maps:is_key(<<"items">>, Schema). + +prepare_validation(Mod) -> + [jesse:add_schema(Def, Schema) + || {Def, Schema} <- Mod:definitions()]. + +validate(Schema, Term) -> + try jesse:validate_with_schema(Schema, Term) catch + Error -> {error, Error} + end. + +validate_request(Mod, OperationId, Args) + when is_list(Args) -> + validate_request(Mod, OperationId, + maps:from_list(Args)); +validate_request(Mod, OperationId, Args) + when is_map(Args) -> + begin + prepare_validation(Mod), + #{parameters := Parameters} = + Mod:operation(OperationId), + ToCheck = [Param + || Param <- Parameters, + not lists:member({"in", "path"}, Param)], + Errors = lists:foldl(fun (Param, Errs) -> + Name = proplists:get_value("name", Param), + case {proplists:get_value("required", Param, + false), + get_by_similar_key(Name, Args)} + of + {false, undefined} -> Errs; + {true, undefined} -> + [{required, Name, Param, OperationId} + | Errs]; + {_, {_, Value}} -> + case + validate(proplists:get_value("schema", + Param, + #{}), + Value) + of + {error, E} -> [E | Errs]; + _ -> Errs + end + end + end, + [], ToCheck), + case Errors of + [] -> ok; + _ -> {errors, {Mod, OperationId, Args, Errors}} + end + end. + +validate_response(Mod, OperationId, StatusCode, Headers, + RawBody) -> + begin + ContentType = + content_type(proplists:get_value("content-type", + Headers)), + {ok, Body} = parse_body(ContentType, + iolist_to_binary(RawBody)), + #{responses := Resps} = Mod:operation(OperationId), + prepare_validation(Mod), + case maps:get(StatusCode, Resps, error) of + error -> {error, {StatusCode, unspecified}}; + [] -> {ok, StatusCode, Body}; + ContentTypes -> + case get_matching(ContentType, ContentTypes) of + #{schema := Schema} -> + case validate(Schema, Body) of + {ok, _} -> {ok, StatusCode, Body}; + {error, E} -> {error, {validation, E}} + end; + undefined -> {ok, StatusCode, Body}; + no_match -> {error, {content_type, ContentType}} + end + end + end. + +http_request(#{url := Url} = Req, HttpOpts, Opts, Cfg) + when is_binary(Url) -> + http_request(Req#{url := binary_to_list(Url)}, HttpOpts, + Opts, Cfg); +http_request(#{method := Method, url := Url, + headers := Headers} = + Req, + HttpOpts, Opts, Cfg) -> + begin + {ok, ClientPid} = inets:start(httpc, + [{profile, test_browser}], stand_alone), + Resp = case Method of + get -> + log("GET ~p", [Url], Cfg), + httpc:request(Method, {Url, Headers}, HttpOpts, + [{socket_opts, [{reuseaddr, true}]} | Opts], + ClientPid); + _ -> + ContentType = maps:get(content_type, Req, ""), + Body = maps:get(body, Req, ""), + log("~p ~p~nContentType ~p~n~p", + [Method, Url, ContentType, Body], Cfg), + httpc:request(Method, {Url, Headers, ContentType, Body}, + HttpOpts, + [{socket_opts, [{reuseaddr, true}]} | Opts], + ClientPid) + end, + ok = gen_server:stop(ClientPid, normal, infinity), + process_response(Resp, Cfg) + end. + +process_response({ok, {{_, Code, Msg}, Headers, Body}}, + Cfg) -> + begin + log("Return code: ~p (~p), Headers: ~p", + [Code, Msg, Headers], Cfg), + {ok, Code, Headers, Body} + end; +process_response({error, Reason} = Error, Cfg) -> + begin log("Return error: ~p", [Reason], Cfg), Error end. + +parse_body("application/json", <<>>) -> {ok, #{}}; +parse_body("application/json", Body) -> + {ok, + jsx:decode(Body, + [{labels, attempt_atom}, return_maps])}; +parse_body("text/plain;charset=UTF-8", Bin) -> + {ok, unicode:characters_to_binary(Bin, unicode, utf8)}; +parse_body(_, Bin) -> {ok, Bin}. + +log(Fmt, Params, Cfg) -> + case proplists:get_value(logfun, Cfg) of + undefined -> ok; + F when is_function(F) -> F(Fmt, Params) + end. + +make_body(Params, _Path) when is_map(Params) -> + {"application/json", jsx:encode(Params)}; +make_body(Bin, _Path) when is_binary(Bin) -> + {"application/octet-stream", Bin}; +make_body([], Path) -> + {"application/x-www-form-urlencoded", + http_uri:encode(Path)}. + +content_type("application/json" ++ _) -> + "application/json"; +content_type(Other) -> Other. + +get_by_similar_key(Name, KVs, Array) -> + case Array of + true -> + case get_by_similar_key(Name, KVs) of + undefined -> + {ArrayName, _} = string:take(Name, "[]", false, + trailing), + get_by_similar_key(ArrayName, KVs); + Other -> Other + end; + _ -> get_by_similar_key(Name, KVs) + end. + +get_by_similar_key(Name, KVs) when is_list(Name) -> + case maps:get(Name, KVs, undefined) of + undefined -> + case maps:get(iolist_to_binary(Name), KVs, undefined) of + undefined -> + try AtomName = list_to_existing_atom(Name), + {AtomName, maps:get(AtomName, KVs)} + catch + _:_ -> undefined + end; + BinValue -> {iolist_to_binary(Name), BinValue} + end; + Value -> {Name, Value} + end. + +get_matching(Key, KVs) when is_list(KVs) -> + case {Key, KVs} of + {undefined, []} -> undefined; + {undefined, [{_, V} | _]} -> V; + _ -> + proplists:get_value(string:to_lower(Key), KVs, no_match) + end. + +to_str(Str) -> + begin + if is_list(Str) -> + binary_to_list(iolist_to_binary(Str)); + is_binary(Str) -> binary_to_list(Str); + true -> lists:concat([Str]) + end + end. \ No newline at end of file diff --git a/test/integration_test.ts b/test/integration_test.ts new file mode 100644 index 0000000000000000000000000000000000000000..cd6072f3539badddbf946c973980e4ad38c190f5 --- /dev/null +++ b/test/integration_test.ts @@ -0,0 +1,3 @@ +{node, mq, 'mq@127.0.0.1'}. + +{suites, ".", [server_SUITE]}. \ No newline at end of file diff --git a/test/nynja.erl b/test/nynja.erl new file mode 100644 index 0000000000000000000000000000000000000000..d373da056793df470b6a21b16274a287358c020d --- /dev/null +++ b/test/nynja.erl @@ -0,0 +1,670 @@ +%%% Created : 25 Feb 2020 by Thomas Arts +-module(nynja). + +-include_lib("emqttc/include/emqttc_packet.hrl"). +-include_lib("roster/include/roster.hrl"). + +-compile([export_all, nowarn_export_all]). + +-define(HOST, os:getenv("NYNJA_HOST", "127.0.0.1")). +%% make this into opaque type +-record(user, {conn, + client_pid :: pid() | undefined, + client :: binary(), + phone = undefined :: undefined | binary(), + roster_id = <<>> :: binary(), + roster_ix = 1 :: integer(), + roster_uuid = <<>> :: binary(), + phone_uuid = <<>> :: binary()}). + +-define(FAKE_CODE, <<"903182">>). +-define(FAKE_PHONES, [<<"380500000000">>, <<"819036705059">>, <<"79999999999">>, <<"817012345678">>]). + +%% High level operations + +connect_sys() -> + User0 = ws_connect(), + + ClientId0 = pick_client_id(<<"sys">>), + ClientId = <<"sys_micro_", ClientId0/binary>>, + + User = User0#user{client = ClientId}, + + #mqtt_packet{ variable = #mqtt_packet_connack{} } = + ws_send(User, mqtt_connect(ClientId, <<"micro">>, <<>>)), + + User. + +register_profile(Info) -> + Sys = connect_sys(), + User = register_profile(Sys, Info), + ws_close(Sys), + User. + +register_profile(Sys, Info) -> + register_profile(Sys, Info, true). + +register_profile(Sys, Phone, Connect) when is_binary(Phone) -> + register_profile(Sys, #{ phone => Phone, fname => Phone, + lname => Phone, nick => Phone }, Connect); +register_profile(Sys, Info, Connect) -> + Phone = maps:get(phone, Info), + + PhoneLink = phone_uuid(Phone), + RosterUUID = roster_uuid(Phone), + + Roster = #'Roster'{ id = RosterUUID, + names = maps:get(fname, Info, []), + surnames = maps:get(lname, Info, []), + nick = maps:get(nick, Info), + phone = PhoneLink, + update = now_msec(), + status = create }, + + Profile = #'Profile'{ phone = PhoneLink, rosters = [Roster], update = now_msec(), status = create }, + + #mqtt_packet{ payload = #'Profile'{rosters = [#'Roster'{}]} } = do_register_profile(Sys, Profile), + case Connect of + true -> connect_user(Phone, PhoneLink, RosterUUID); + false -> ok + end. + +connect_user(Phone) -> + PhoneLink = phone_uuid(Phone), + RosterUUID = roster_uuid(Phone), + + connect_user(Phone, PhoneLink, RosterUUID). + +connect_user(Phone, PhoneLink, RosterUUID) -> + Token = jwt_token(RosterUUID), + User0 = ws_connect([{<<"x-json-web-token">>, Token}]), + ClientId = <<"emqttd_", (pick_client_id(Phone))/binary>>, + + User = User0#user{ client = ClientId, phone = Phone, + roster_uuid = RosterUUID, phone_uuid = PhoneLink }, + + connect_micro(User, ClientId), + + timer:sleep(25), %% avoid race between subscriptions and get_profile + + #mqtt_packet{ payload = #'Profile'{ rosters = [#'Roster'{ id = RosterIx }] } } = get_profile(User, 5000), + + RosterId = make_phone_id(PhoneLink, RosterIx), + + User#user{ roster_ix = RosterIx, roster_id = RosterId }. + +register_phone(Phone) -> + register_phone(Phone, true). + +register_phone(Phone, WithRosterId) -> + RegUser = ws_connect(), + {ClientId, Token} = reg(RegUser, Phone), + + User0 = ws_connect(), + connect(User0, {ClientId, Token}), + + User = User0#user{client = ClientId, phone = Phone}, + + RosterIx = + case WithRosterId of + true -> + #mqtt_packet{ payload = #'Profile'{ rosters = Rs } } = get_profile(User), + #'Roster'{ id = RosterIx0 } = hd(Rs), + RosterIx0; + false -> + 1 + end, + + RosterId = iolist_to_binary([Phone, "_", integer_to_list(RosterIx)]), + + User#user{roster_id = RosterId, roster_ix = RosterIx}. + +become_friends(#user{roster_id = RosterId1} = User1, + #user{roster_id = RosterId2} = User2) -> + #mqtt_packet{ payload = {io, _, #'Roster'{}} } = + search_contact(User2, user_phone(User1)), + + send_friend_req(User2, RosterId1), + catch ws_receive(User1, 1, 1000), + + #mqtt_packet{ payload = #'Contact'{ last_msg = #'Message'{ feed_id = FeedId } } } = + friend_req_accept(User1, RosterId2), + ws_receive(User2), %% last_msg + + FeedId. + +room_feed(Room) -> + #muc{ name = typed_uuid(<<"room">>, Room) }. + +feed_id(#user{roster_id = RId1}, #user{ roster_id = RId2 }) -> + list_to_tuple([p2p | lists:sort([RId1, RId2])]). + +send_message(#user{roster_id = RosterId1} = User1, User2, Msg, FeedId) -> + + %% timer:sleep(500), + %% io:format("Sending message from ~s to ~s\n", [ClientId2, ClientId1]), + send_message_(User2, Msg, RosterId1, FeedId), + [#mqtt_packet{ payload = #'Message'{ id = MsgId } }] = ws_receive(User1), + + %% timer:sleep(500), + %% io:format("Client 1 gets messages\n"), + get_messages(User1, FeedId, MsgId), + + %% timer:sleep(500), + %% io:format("Client 1 marks message 2 as read\n"), + read_message(User1, FeedId, MsgId), + ws_receive(User2), %% message read notification + + MsgId. + +create_room(User = #user{ client = ClientId, roster_id = PhoneId }, + Info = #{ name := Name }) -> + RoomId = typed_uuid(<<"room">>, Name), + Owner = make_member(#{ phone_id => PhoneId, room_id => RoomId, status => admin }), + Room = #'Room'{ id = RoomId, name = Name, + members = maps:get(members, Info, []), + admins = [Owner], + type = group, tos = maps:get(desc, Info, []), + status = create }, + + ws_send(User, mqtt_publish(ClientId, Room)). + +join_room(User = #user{ client = ClientId, roster_id = PhoneId }, RoomName, Status) -> + RoomId = typed_uuid(<<"room">>, RoomName), + Member = make_member(#{ phone_id => PhoneId, room_id => RoomId, status => Status }), + Room = #'Room'{ id = RoomId, members = [Member], status = join }, + ws_send(User, mqtt_publish(ClientId, Room)). + +leave_room(User = #user{ client = ClientId, roster_id = PhoneId }, RoomName, Status) -> + RoomId = typed_uuid(<<"room">>, RoomName), + Member = make_member(#{ phone_id => PhoneId, status => Status }), + Room = #'Room'{ id = RoomId, members = [Member], status = leave }, + ws_send(User, mqtt_publish(ClientId, Room)). + +remove_from_room(User = #user{ client = ClientId }, Phone, RoomName, Status) -> + RoomId = typed_uuid(<<"room">>, RoomName), + case get_member_phone_id(User, Phone, RoomName) of + false -> not_in_room; + PhoneId -> + Member = make_member(#{ phone_id => PhoneId, status => Status }), + Room = #'Room'{ id = RoomId, members = [Member], status = remove }, + ws_send(User, mqtt_publish(ClientId, Room)) + end. + +delete_room(RoomName) -> + Sys = connect_sys(), + delete_room(Sys, RoomName), + ws_close(Sys). + +delete_room(Sys = #user{ client = SysId }, RoomName) -> + RoomId = typed_uuid(<<"room">>, RoomName), + Room = #'Room'{ id = RoomId, status = delete }, + ws_send(Sys, mqtt_publish(SysId, Room), 0, 100). %% no reply?! + +get_member_phone_id(User, Alias, RoomName) -> + #mqtt_packet{ payload = Room } = get_room(User, RoomName), + Members = Room#'Room'.admins ++ Room#'Room'.members, + case lists:keyfind(Alias, #'Member'.alias, Members) of + #'Member'{ phone_id = PhoneId } -> PhoneId; + false -> false + end. + +get_room(User, Room) -> + RoomId = typed_uuid(<<"room">>, Room), + get_room_info(User, RoomId, get). + +get_room_info(User = #user{ client = ClientId }, RoomId, What) -> + ws_send(User, mqtt_publish(ClientId, #'Room'{ id = RoomId, status = What })). + +msg_room(User, Room, Msg) -> + msg_room(User, Room, Msg, false). + +msg_room(User = #user{ client = ClientId }, Room, Msg, Ack) -> + MsgS = make_room_message(User, Room, Msg, Ack), + N = if Ack -> 2; true -> 1 end, + ws_send(User, mqtt_publish(ClientId, MsgS), N, 5000). + +make_room_message(User, RoomName, Msg) -> + make_room_message(User, RoomName, Msg, false). + +make_room_message(User, RoomName, Msg, Ack) -> + RoomId = typed_uuid(<<"room">>, RoomName), + make_message(User, RoomId, #muc{ name = RoomId }, Msg, Ack). + +make_message(#user{ client = ClientId, roster_id = PhoneId }, To, Feed, Msg, Ack) -> + TS = integer_to_binary(now_usec()), + ID = <>, + PayloadDesc = + case Msg of + [] -> []; + _ when is_binary(Msg) -> + Feat = #'Feature'{ id = <>, key = <<"SIZE">>, + value = integer_to_binary(byte_size(Msg)), group = <<"FILE_DATA">> }, + [ #'Desc'{ id = ID, mime = <<"text">>, payload = Msg, data = [Feat] } ] + end, + AckDesc = [ #'Desc'{ id = <<"ack", TS/binary>>, mime = <<"ack">> } || Ack ], + #'Message'{ feed_id = Feed, msg_id = typed_uuid(PhoneId, TS), from = PhoneId, + to = To, files = PayloadDesc ++ AckDesc }. + +make_member(#{ phone_id := PhoneId } = Args) -> + Settings = + case Args of + #{ room_id := RoomId } -> + [{'Feature', <>, <<"DEFAULT_LANGUAGE">>, <<"English:en">>, <<"LANGUAGE_SETTING">>}, + {'Feature', <>, <<"NOTIFICATIONS">>, <<"true">>, <<"GROUP_NOTIFICATIONS">>}]; + _ -> [] + end, + #'Member'{ phone_id = PhoneId, + update = now_msec(), + settings = Settings, + status = maps:get(status, Args, member) }. + +%% Single operations + +do_register_profile(#user{client = ClientId} = User, Profile) -> + ws_send(User, mqtt_publish(ClientId, Profile)). + +get_profile_chunked(#user{client = ClientId} = User, ChunkSize, Chunks, TO) -> + Packets = [Pkt | _] = ws_send(User, mqtt_publish(ClientId, profile_get_chunked(user_phone(User), ChunkSize)), Chunks, TO), + Roster1 = hd(Pkt#mqtt_packet.payload#'Profile'.rosters), + {Us, Rs, Ss} = + lists:unzip3([ {U, R, S} || #mqtt_packet{ payload = Payload } <- Packets, + #'Profile'{ rosters = [#'Roster'{ userlist = U, + roomlist = R, + favorite = S }] } <- [Payload] ]), + U = lists:append(Us), + R = lists:append(Rs), + S = lists:append(Ss), + Pkt#mqtt_packet{ payload = Pkt#mqtt_packet.payload#'Profile'{ rosters = [Roster1#'Roster'{ userlist = U, roomlist = R, favorite = S }] } }. + +get_chunks(_, 0, _, Acc) -> lists:reverse(Acc); +get_chunks(User, Chunks, TO, Acc) -> + [Res] = ws_receive(User, 1, TO), + get_chunks(User, Chunks - 1, TO, [Res | Acc]). + +get_profile(User) -> get_profile(User, 5000). +get_profile(User, TO) -> + get_profile_(User, TO). + +get_profile_(User, TO) -> + get_profile1(User, TO). + +get_profile1(User = #user{client = ClientId}, Timeout) -> + Phone = user_phone(User), + ws_send(User, mqtt_publish(ClientId, profile_get(Phone)), 1, Timeout). + +set_name(#user{client = ClientId, roster_ix = RosterIx} = User, FName, LName) -> + ws_send(User, mqtt_publish(ClientId, roster_patch(RosterIx, FName, LName))). + +set_nick(#user{client = ClientId, roster_ix = RosterIx} = User, NName) -> + ws_send(User, mqtt_publish(ClientId, roster_nick(RosterIx, NName))). + +set_avatar(#user{client = ClientId, roster_ix = RosterIx} = User, Bin) -> + ws_send(User, mqtt_publish(ClientId, roster_avatar(RosterIx, Bin))). + +search_contact(#user{ client = ClientId } = User, Phone) -> + ws_send(User, mqtt_publish(ClientId, search_by_phone(Phone))). + +send_friend_req(#user{ client = ClientId, roster_id = From } = User, To) -> + ws_send(User, mqtt_publish(ClientId, friend_req(From, To))). + +friend_req_accept(#user{ client = ClientId, roster_id = From } = User, To) -> + case ws_send(User, mqtt_publish(ClientId, friend_req_accept_msg(From, To)), 2, 5000) of + [M = #mqtt_packet{ payload = #'Contact'{} }, _] -> M; + [_, M = #mqtt_packet{ payload = #'Contact'{} }] -> M + end. + +send_message_(User, Msg, To, FeedId) -> + send_message_(User, Msg, To, FeedId, false). + +send_message_(User = #user{ client = ClientId }, Msg, To, FeedId, Ack) -> + ws_send(User, mqtt_publish(ClientId, make_message(User, To, FeedId, Msg, Ack))). + +get_messages(User, FeedId, MsgId) -> get_messages(User, FeedId, MsgId, -30). + +get_messages(User = #user{ client = ClientId, roster_id = RId }, FeedId, MsgId, Count) -> + ws_send(User, mqtt_publish(ClientId, get_msg(RId, FeedId, MsgId, Count))). + +read_message(User = #user{ client = ClientId }, FeedId, MsgId) -> + ws_send(User, mqtt_publish(ClientId, read_msg(User, FeedId, MsgId)), 2, 5000). + +mqtt_connect(ClientId, Username, Password) -> + #mqtt_message{qos = WillQos, + retain = WillRetain, + topic = WillTopic, + payload = WillMsg} = #mqtt_message{topic = <<"version/11">>, + payload = <<>>}, + %% if wilol msg and topic is omitted: + %% 020-02-28 14:13:37.047 [info] <0.1150.0>@emqttd_protocol:trace:329 Client(undefined@10.211.55.2:65077): RECV CONNECT(Q0, R0, D0, ClientId=reg_eglhrhgr37wk74lvznu, ProtoName=MQTT, ProtoVsn=4, CleanSess=true, KeepAlive=90, Username=api, Password=******) + %% 2020-02-28 14:13:37.047 [error] <0.1150.0>@emqttd_protocol:process:203 Client(reg_eglhrhgr37wk74lvznu@10.211.55.2:65077): Username 'api' login failed for invalid_version + + Connect = #mqtt_packet_connect{client_id = ClientId, + proto_ver = 4, + proto_name = <<"MQTT">>, + will_flag = true, + will_retain = WillRetain, + will_qos = WillQos, + clean_sess = true, + keep_alive = ?KEEPALIVE, + will_topic = WillTopic, + will_msg = WillMsg, + username = Username, + password = Password}, + ?CONNECT_PACKET(Connect). + +mqtt_pubrec(PackId) -> + ?PUBACK_PACKET(?PUBREC, PackId). + +mqtt_pubcomp(PackId) -> + ?PUBACK_PACKET(?PUBCOMP, PackId). + +mqtt_publish_reg(ClientId, Msg) -> + mqtt_publish(<<"reg_", ClientId/binary>>, Msg). + +mqtt_publish(ClientId, Msg) -> + Node = integer_to_binary(rand:uniform(4)), + Topic = <<"events/1/", Node/binary, "/api/anon/", ClientId/binary, "/">>, + PacketId = undefined, + %% io:format("Publish: ~99999p\n", [Msg]), + Payload = term_to_binary(Msg), + ?PUBLISH_PACKET(?QOS_0, Topic, PacketId, Payload). + +auth_rec(ClientId, Phone) -> + {'Auth', <<"reg_", ClientId/binary>>, ClientId, [], Phone, + [], reg, [], [], [], [], [], [], [], []}. + +auth_verify(ClientId, Phone, Code) -> + {'Auth', [], ClientId, [], Phone, [], + verify, Code, [],[], + [{'Feature',<<"554324f4-f876-4581-ad39-c232db59a798">>, + <<"DEFAULT_LANGUAGE">>,<<"English:en">>, + <<"LANGUAGE_SETTING">>}], + [],[],[],[]}. + +profile_get_chunked(Phone, ChunkSize) -> + Pagination = #'Feature'{ key = <<"size">>, + value = integer_to_binary(ChunkSize), + group = <<"PAGINATION">> }, + #'Profile'{ phone = Phone, settings = [Pagination], status = get }. + +profile_get(Phone) -> + #'Profile'{ phone = Phone, status = get }. + +roster_get(RId) -> + {'Roster', RId, [], [], [], [], [], [], [], [], [], [], [], get}. + +roster_patch(RosterIx, FName, LName) -> + {'Roster', RosterIx, FName, LName, [], [], [], [], [], [], [], [], [], patch}. + +roster_nick(RosterIx, Nick) -> + {'Roster', RosterIx, [], [], [], Nick, [], [], [], [], [], [], [], nick}. + +roster_avatar(RosterIx, Avatar) -> + {'Roster', RosterIx, [], [], [], [], [], [], [], [], [], Avatar, [], patch}. + +search_by_phone(Phone) -> + {'Search', 3, <<"phone">>, <<"phone">>, '==', [Phone], contact}. + +friend_req(From, To) -> + {'Friend', From, To, [], request}. + +friend_req_accept_msg(From, To) -> + {'Friend', From, To, [], confirm}. + +get_msg(RId, FeedId, MsgId, Count) -> + #'History'{ roster_id = RId, feed = FeedId, size = Count, + entity_id = MsgId, status = get }. + +read_msg(#user{ roster_id = RId }, FeedId, MsgId) -> + #'History'{ roster_id = RId, feed = FeedId, entity_id = MsgId, status = update }. + +delete_hist(FeedId) -> + #'History'{ feed = FeedId, status = delete }. + +user_conn(#user{conn = Conn}) -> + Conn. + +user_pid (#user{ client_pid = ClientPid }) -> ClientPid. +user_client(#user{ client = ClientId }) -> ClientId. +user_roster(#user{ roster_id = RosterId }) -> RosterId. + +user_phone_uuid(#user{ phone_uuid = PhoneUUID }) -> PhoneUUID. +user_roster_uuid(#user{ roster_uuid = RosterUUID }) -> RosterUUID. + +%% prioritize phone_uuid +user_phone(#user{phone = undefined, phone_uuid = <<>>}) -> []; +user_phone(#user{phone = Phone, phone_uuid = <<>>}) -> Phone; +user_phone(#user{phone_uuid = Phone}) -> Phone. + +user_client_pid(#user{client_pid = Pid}) -> Pid. + +pick_client_id(Phone) -> + Fresh = iolist_to_binary(io_lib:format("~8.32.0b", [rand:uniform(1 bsl 40) - 1])), + <>. + +reg(User, Phone) -> + ClientId = pick_client_id(Phone), + #mqtt_packet{ variable = #mqtt_packet_connack{} } = + ws_send(User, mqtt_connect(<<"reg_", ClientId/binary>>, <<"api">>, <<>>)), + + Auth = auth_rec(ClientId, Phone), + ws_send(User, mqtt_publish_reg(ClientId, Auth)), + + AuthVerify = auth_verify(ClientId, Phone, ?FAKE_CODE), + #mqtt_packet{payload = Pl} = ws_send(User, mqtt_publish_reg(ClientId, AuthVerify)), + {io, {ok2, login, {UserId, Token}}, <<>>} = Pl, + + ws_close(User), + + {UserId, Token}. + +connect(Connection, {User, Token}) -> + #mqtt_packet{ variable = #mqtt_packet_connack{} } = + ws_send(Connection, mqtt_connect(User, <<"api">>, Token)). + +connect_micro(User, ClientId) -> + #mqtt_packet{ variable = #mqtt_packet_connack{} } = + ws_send(User, mqtt_connect(ClientId, <<"micro">>, <<>>)). + +ws_connect() -> + ws_connect([]). +ws_connect(Hdrs) -> + Root = self(), + Pid = spawn_link(fun() -> + Conn = ws_connect_(Hdrs), + Root ! {self(), Conn}, + ws_loop(Root, Conn, []) + end), + receive + {Pid, Conn} -> #user{conn = Conn, client_pid = Pid} + end. + +ws_loop(Root, Conn = {ConnPid, MRef, StreamRef}, Acks) -> + receive + close -> ws_close(Conn); + {send, Packet} -> + gun_ws_send(Conn, Packet), + ws_loop(Root, Conn, Acks); + {gun_ws, ConnPid, StreamRef, Frame} -> + Msg = decode_frame(Frame), + case Msg of + #mqtt_packet{ header = #mqtt_packet_header{ qos = QoS }, + variable = #mqtt_packet_publish{ packet_id = PackId } } -> + %% io:format("Got msg ~p\n", [PackId]), + Acks1 = + case QoS == ?QOS_2 of + true -> gun_ws_send(Conn, mqtt_pubrec(PackId)), + [PackId | Acks]; + false -> Acks + end, + [ Root ! {self(), Msg} || not ignore_packet(Msg) ], + ws_loop(Root, Conn, Acks1); + #mqtt_packet{ header = #mqtt_packet_header{ type = ?PUBREL }, + variable = #mqtt_packet_puback{ packet_id = PackId } } -> + %% io:format("Acked msg ~p\n", [PackId]), + case lists:member(PackId, Acks) of + false -> exit({unexpected_ack, PackId, Acks}); + true -> + gun_ws_send(Conn, mqtt_pubcomp(PackId)), + ws_loop(Root, Conn, Acks -- [PackId]) + end; + ?PACKET(?PINGRESP) -> + ws_loop(Root, Conn, Acks); + _ -> %% CONNACK etc. + Root ! {self(), Msg}, + ws_loop(Root, Conn, Acks) + end; + {'DOWN', MRef, process, ConnPid, Reason} -> + error_logger:error_msg("Oops!"), + exit(Reason); + Other -> + io:format("Unexpected message: ~p\n", [Other]), + ws_loop(Root, Conn, Acks) + after 240000 -> + gun_ws_send(Conn, ?PACKET(?PINGREQ)), + ws_loop(Root, Conn, Acks) + end. + +ws_connect_(Hdrs) -> + {ok, _} = application:ensure_all_started(gun), + {ok, ConnPid} = gun:open(?HOST, 8083), + MRef = monitor(process, ConnPid), + {ok, http} = gun:await_up(ConnPid), + gun:ws_upgrade(ConnPid, "/mqtt", Hdrs, #{protocols => [{<<"mqtt">>, gun_ws_h}]}), + receive + {gun_upgrade, ConnPid, StreamRef, [<<"websocket">>], _Headers} -> + {ConnPid, MRef, StreamRef}; + + {gun_response, ConnPid, _, _, Status, Headers} -> + exit({ws_upgrade_failed, Status, Headers}); + + {gun_error, ConnPid, _StreamRef, Reason} -> + exit({ws_upgrade_error, Reason}) + after 1000 -> + exit(timeout) + end. + +ws_close(#user{ client_pid = Pid }) when is_pid(Pid) -> + Pid ! close; +ws_close({ConnPid, _, StreamRef}) when is_pid(ConnPid) -> + gun:cancel(ConnPid, StreamRef), + gun:close(ConnPid); +ws_close(_) -> ok. + +ws_receive(Conn) -> + ws_receive(Conn, 1, 5000). + +ws_receive(Conn, Msgs) -> + ws_receive(Conn, Msgs, 5000). + +ws_receive(Conn, Msgs, TO) -> + ws_receive(Conn, [], Msgs, TO). + +ws_receive(#user{ client_pid = Pid }, Msgs, NMsgs, TO) when is_pid(Pid) -> + ws_receive_(Pid, Msgs, NMsgs, TO). + +ignore_packet(#mqtt_packet{ variable = #mqtt_packet_publish{}, payload = Payload }) -> + case Payload of + #'Contact'{status = internal} -> true; + _ -> false + end. + +ws_receive_(_, Msgs, 0, _TO) -> + lists:reverse(Msgs); +ws_receive_(Pid, Msgs, MsgsLeft, TO) -> + receive + {Pid, Msg = #mqtt_packet{}} -> + [ exit({extra_msg, Msg, Msgs}) || MsgsLeft == 0 ], + ws_receive_(Pid, [Msg | Msgs], dec(MsgsLeft), TO) + after TO -> + case MsgsLeft of + any -> + lists:reverse(Msgs); + _ -> + %% io:format("TIMEOUT: ~p\n", [MsgsLeft]), + timeout + end + end. + +dec(any) -> any; +dec(N) -> N - 1. + +gun_ws_send({ConnPid, _, _}, MQTTPacket) -> + ok = gun:ws_send(ConnPid, {binary, emqttc_serialiser:serialise(MQTTPacket)}). + +ws_send_async(#user{ client_pid = Pid }, Packet) when is_pid(Pid) -> + Pid ! {send, Packet}; +ws_send_async(#user{ conn = Conn }, Packet) -> + gun_ws_send(Conn, Packet). + +ws_send(User, Packet) -> + ws_send(User, Packet, 1, 5000). + +ws_send(User, Packet, Replies, TO) -> + ws_send_async(User, Packet), + case ws_receive(User, Replies, TO) of + [Msg] -> Msg; + Msgs -> Msgs + end. + +decode_frame({binary, Bin}) -> + case emqttc_parser:parse(Bin, none) of + {ok, #mqtt_packet{variable = #mqtt_packet_connack{}} = Mqtt, <<>>} -> + Mqtt#mqtt_packet{ payload = <<>> }; + {ok, #mqtt_packet{payload = Payload} = Mqtt, <<>>} -> + Resp = + case Payload of + <<>> -> Mqtt; + undefined -> Mqtt; + _ -> Mqtt#mqtt_packet{payload = binary_to_term(Payload)} + end, + %% io:format("Decoded Frame: ~p\n", [Resp]), + Resp + end. + +now_usec() -> + {A, B, C} = os:timestamp(), + C + 1000000 * (B + 1000000 * A). + +now_msec() -> now_usec() div 1000. +now_sec() -> now_usec() div 1000000. + +new_uuid() -> + {A, B, C} = os:timestamp(), + Bin = <<(integer_to_binary(A))/binary, + (integer_to_binary(B))/binary, + (integer_to_binary(C))/binary>>, + list_to_binary(uuid:uuid_to_string(uuid:get_v3(Bin))). + +phone_uuid(Phone) -> + typed_uuid(<<"phone">>, Phone). + +roster_uuid(Phone) -> + typed_uuid(<<"roster">>, Phone). + +typed_uuid(Type, Value) -> + list_to_binary(uuid:uuid_to_string(uuid:get_v3(Type, Value))). + +make_phone_id(PhoneLink, RosterIx) -> + iolist_to_binary([PhoneLink, "_", integer_to_list(RosterIx)]). + +jwt_token(RosterUUID) -> + Time = now_sec(), + JWTMap = #{ <<"aud">> => <<"MTIz:NynjaWeb:Nynja">>, + <<"exp">> => Time + 3600, + <<"iat">> => Time, + <<"iss">> => <<"https://auth.nynja.biz/">>, + <<"roles">> => [<<"USER">>], + <<"scope">> => <<"access">>, + <<"sub">> => base64:encode(RosterUUID) }, + + {_, Priv} = jose_jwa:generate_key(<<"ES256">>), + + jose_jws_compact:encode(JWTMap, <<"ES256">>, Priv). + + +msgs_split_topic(TopicPrefix, Msgs) -> + lists:partition(fun(#mqtt_packet{ variable = #mqtt_packet_publish{ topic_name = Topic}}) -> + string:prefix(Topic, TopicPrefix) /= nomatch; + (_) ->false + end, Msgs). diff --git a/test/server_SUITE.erl b/test/server_SUITE.erl new file mode 100644 index 0000000000000000000000000000000000000000..7b85d703632ea56a489d7bbed62ba4b63a8af387 --- /dev/null +++ b/test/server_SUITE.erl @@ -0,0 +1,295 @@ +-module(server_SUITE). +-include_lib("stdlib/include/assert.hrl"). + +%% common_test exports +-export([ all/0 + , groups/0 + , init_per_group/2 + , init_per_suite/1 + , end_per_suite/1 + , end_per_group/2 + , init_per_testcase/2 + , end_per_testcase/2 + ]). + +%% Tests +-export([ register_user/1 + , fake_numbers/1 + , sessions/1 + , whitelist/1 + , metrics/1 + , rooms/1 + ]). + +all() -> + [{group, primitives} + ]. + +groups() -> + [ {primitives, [register_user, sessions, fake_numbers, whitelist, metrics, rooms]} + ]. + +init_per_suite(Cfg) -> + case os:getenv("NYNJA_HOST") of + false -> + ct:pal("NYNJA_HOST undefined. Suite requires running nynja server!"), + {skip, {undefined, "NYNJA_HOST"}}; + Host -> + %% Use a random phoen number for testing. Skip tests if number has already been used + Phone = rand:uniform(1000000), + + %% Rebar config has ct_opts point to sys.config such that we + %% have access to config parameters + %% In case one tests against a differently configured node, + %% provide ct_opts with that configuration! + + {ok, Port} = application:get_env(rest, port), + BaseUrl = iolist_to_binary(["http://", Host, ":", integer_to_list(Port), "/"]), + {ok, 200, Response} = request('IsAlive', [], [{base_url, BaseUrl}]), + ct:log("Pinged server resulted in ~p", [Response]), + %% this does not mean that we "Action Process" is passed + [{base_url, BaseUrl}, {phone, iolist_to_binary(integer_to_list(Phone))} | + Cfg] + end. + +end_per_suite(Cfg) -> + Cfg. + +init_per_group(_Grp, Cfg) -> + Cfg. + +end_per_group(_Grp, Cfg) -> + Cfg. + +init_per_testcase(sessions, Cfg) -> + case application:get_env(roster, get_sessions_api, false) of + false -> + {ok, 404, _} = request('GetSessions', #{}, [basic_auth() | Cfg]), + {skip, get_session_api_disabled_in_sys_config}; + true -> + Cfg + end; +init_per_testcase(whitelist, Cfg) -> + case application:get_env(roster, whitelist_check, false) of + false -> + {ok, 404, _} = request('GetWhiteList', #{}, [basic_auth() | Cfg]), + {skip, whitelist_check_disabled_in_sys_config}; + true -> + Cfg + end; +init_per_testcase(_TC, Cfg) -> + Cfg. + +end_per_testcase(_TC, Cfg) -> + Cfg. + +%%% ------------- tests ----------------------- + +register_user(Cfg) -> + Phone = proplists:get_value(phone, Cfg, <<"000000033">>), + ct:log("Phone = ~p", [Phone]), + try User = nynja:register_profile(#{ phone => Phone, fname => <<"Tes">>, + lname => <<"Tester">>, nick => Phone }), + nynja:ws_close(User), + ok + catch + error:{badmatch, {mqtt_packet,_,_, {io,{error,already_exist},_}}} -> + ct:log("Registering failed: user already exists"), + {skip, user_already_registered} + end. + +sessions(Cfg) -> + Phone = proplists:get_value(phone, Cfg, <<"000000034">>), + register_user(Cfg), + %% Some negative testing + {ok, 401, _} = request('GetSessions', #{phone => Phone}, Cfg), + {ok, 400, _} = request('GetSessions', #{phone => Phone}, [basic_auth() | Cfg]), + + LookupPhone = nynja:phone_uuid(Phone), + + nynja:connect_user(Phone), + {ok, 200, Sessions} = request('GetSessions', #{phone => LookupPhone}, [basic_auth() | Cfg]), + ?assertEqual(1, length(Sessions)), + + %% Connect a second device + nynja:connect_user(Phone), + {ok, 200, Sessions2} = request('GetSessions', #{phone => LookupPhone}, [basic_auth() | Cfg]), + ?assertEqual(2, length(Sessions2)), + + {ok, 401, _} = request('GetSessions', #{phone => LookupPhone}, Cfg), + ok. + +fake_numbers(Cfg) -> + {ok, 200, _HTML} = request('GetFakeNumbers', #{}, [basic_auth() | Cfg]), + %% We cannot test the javascript html page: that's a TODO for GUI tester + + %% Make sure internal API uses authentication + {ok, 401, _} = request('fake_numbers', #{}, Cfg), + + %% Put a new fake number in the list + MyFakeNr1 = 123321123, + MyFakeNr2 = 122331122, + {ok, 200, #{'Status' := <<"Success">>}} = request('put_fake_numbers', #{phone => [MyFakeNr1, MyFakeNr2]}, [basic_auth() | Cfg]), + + {ok, 200, #{'Data' := FakeNumbers, + 'Status' := <<"Success">>}} = request('fake_numbers', #{}, [basic_auth() | Cfg]), + StartNrNumbers = length(FakeNumbers), + ?assert(StartNrNumbers > 0), + Nrs = [ Nr || #{phone := Nr} <- FakeNumbers ], + ?assert(lists:member(MyFakeNr1, Nrs)), + Phone = hd(Nrs), + User = nynja:register_phone(list_to_binary(integer_to_list(Phone))), + ct:log("Registerd fake phone ~p\n", [User]), + {ok, 200, _} = request('delete_fake_numbers', #{phone => lists:concat([MyFakeNr1, ",", MyFakeNr2])}, [basic_auth() | Cfg]), + + {ok, 200, #{'Data' := NewFakeNumbers, + 'Status' := <<"Success">>}} = request('fake_numbers', #{}, [basic_auth() | Cfg]), + Nrs2 = [ Nr || #{phone := Nr} <- NewFakeNumbers ], + ?assert(not lists:member(MyFakeNr2, Nrs2)), + ok. + +whitelist(Cfg) -> + {ok, 200, _HTML} = request('GetWhiteList', #{}, [basic_auth() | Cfg]), + %% We cannot test the javascript html page: that's a TODO for GUI tester + + %% Make sure internal API uses authentication + {ok, 401, _} = request('whitelist', #{}, Cfg), + + %% Put a new fake number in the list + MyFakeNr1 = 123321000, + {ok, 200, #{'Status' := <<"Success">>}} = request('put_whitelist', #{phone => [MyFakeNr1]}, [basic_auth() | Cfg]), + + {ok, 200, #{'Data' := FakeNumbers, + 'Status' := <<"Success">>}} = request('whitelist', #{}, [basic_auth() | Cfg]), + StartNrNumbers = length(FakeNumbers), + ?assert(StartNrNumbers > 0), + Nrs = [ Nr || #{phone := Nr} <- FakeNumbers ], + ?assert(lists:member(MyFakeNr1, Nrs)), + Reg = register_user([{phone, list_to_binary(integer_to_list(MyFakeNr1))} | proplists:delete(phone, Cfg)]), + ct:log("Registerd whitelisted phone ~p\n", [Reg]), + {ok, 200, _} = request('delete_whitelist', #{phone => lists:concat([MyFakeNr1])}, [basic_auth() | Cfg]), + + {ok, 200, #{'Data' := NewFakeNumbers, + 'Status' := <<"Success">>}} = request('whitelist', #{}, [basic_auth() | Cfg]), + Nrs2 = [ Nr || #{phone := Nr} <- NewFakeNumbers ], + ?assert(not lists:member(MyFakeNr1, Nrs2)), + ok. + +metrics(Cfg) -> + {Time, {ok, 200, _Text}} = timer:tc(fun() -> request('Metrics', #{}, Cfg) end), + ct:log("Metrics timing: ~p", [Time]), + ok. + +rooms(Cfg) -> + Phone = proplists:get_value(phone, Cfg, <<"000000035">>), + MemberPhone = <<"002003004">>, + + register_user([{phone, Phone}]), %% may fail if already registered + register_user([{phone, MemberPhone}]), + + AdminUser = nynja:connect_user(Phone), + User = nynja:connect_user(MemberPhone), + AdminId = nynja:user_roster(AdminUser), + MemberId = nynja:user_roster(User), + + RoomName = <<"TestRoom-", Phone/binary>>, + nynja:create_room(AdminUser, #{ name => RoomName}), + RoomId = nynja:typed_uuid(<<"room">>, RoomName), + LookupPhone = nynja:phone_uuid(Phone), + + %% Failed to authenticate before get + ct:log("Missing header parameter"), + {ok, 400, _} = request('GetCRIRoom', #{}, [basic_auth() | Cfg]), + + ct:log("Missing query parameter"), + {ok, 400, _} = request('GetCRIRoom', #{phoneid => LookupPhone}, [basic_auth() | Cfg]), + + ct:log("Wrong header parameter"), + {ok, 400, _} = request('GetCRIRoom', #{phoneid => Phone, room_id => RoomId}, [basic_auth() | Cfg]), + + ct:log("No authentication"), + {ok, 401, _} = request('GetCRIRoom', #{phoneid => AdminId, room_id => RoomId}, Cfg), + + {ok, 200, #{data := #{type := Type}}} = + request('GetCRIRoom', #{phoneid => AdminId, room_id => RoomId}, [basic_auth() | Cfg]), + ct:log("Able to get the type of an existing room ~p", [Type]), + + ct:log("Phone ids should be a real id, not phone number"), + {ok, 200, #{data := [#{error := <<"User Not Found">>, + is_member := <<"false">>, + phone_id := Phone}]}} = + request('GetMembersCRIRoom', + #{phoneid => AdminId, room_id => RoomId, phone_ids => Phone}, [basic_auth() | Cfg]), + + ct:log("User is found, but phone ids should contain phone plus roster ix"), + {ok, 200, #{data := [#{is_member := <<"false">>, + phone_id := LookupPhone}]}} = + request('GetMembersCRIRoom', + #{phoneid => AdminId, room_id => RoomId, phone_ids => LookupPhone}, [basic_auth() | Cfg]), + + %% The AdminId is the one we are looking for + {ok, 200, #{data := [#{is_member := <<"true">>, + phone_id := AdminId}]}} = + request('GetMembersCRIRoom', + #{phoneid => AdminId, room_id => RoomId, phone_ids => AdminId}, [basic_auth() | Cfg]), + + + nynja:join_room(User, RoomName, member), + {ok, 200, #{data := [#{is_member := <<"true">>}]}} = + request('GetMembersCRIRoom', + #{phoneid => AdminId, room_id => RoomId, phone_ids => MemberId}, [basic_auth() | Cfg]), + + {ok, 200, #{data := [#{is_member := <<"true">>}]}} = + request('GetMembersCRIRoom', + #{phoneid => MemberId, room_id => RoomId, phone_ids => AdminId}, [basic_auth() | Cfg]), + + {ok, 200, #{data := Members}} = + request('GetMembersCRIRoom', + #{phoneid => LookupPhone, room_id => RoomId, phone_ids => iolist_to_binary([MemberId, ",", AdminId, ",", Phone])}, + [basic_auth() | Cfg]), + ?assertEqual([<<"false">>,<<"true">>,<<"true">>], lists:sort([ B || #{is_member := B} <- Members ])), + + {ok, 403, _} = + request('DeleteMembersCRIRoom', + #{phoneid => MemberId, room_id => RoomId, phone_ids => iolist_to_binary([MemberId, ",", AdminId])}, + [basic_auth() | Cfg]), + + %% Bug in the software + {ok, 200, #{data := _}} = + request('DeleteMembersCRIRoom', + #{phoneid => AdminId, room_id => RoomId, phone_ids => MemberId}, + [basic_auth() | Cfg]), + + {ok, 200, #{data := [#{is_member := <<"false">>}]}} = + request('GetMembersCRIRoom', + #{phoneid => AdminId, room_id => RoomId, phone_ids => MemberId}, [basic_auth() | Cfg]), + + + ok. + + + + + +%%% ------------- helpers --------------------- + +request(Id, Params, Config) -> + Request = api_nynja:request(Id, Params, Config), + ct:pal("Outgoing Request ~p", [Request]), + + {ok, Status, Headers, Body} = + api_nynja:http_request(Request, [], [], + [{logfun, fun(F, As) -> + ct:log(F, As) + end}]), + Response = api_nynja:validate_response(Id, Status, Headers, Body), + [ ct:log("Validated ~p\n", [Response]) || true], + Response. + +basic_auth() -> + %% Get the User and Password from sys.config + {ok, BasicAuth} = application:get_env(rest, basic_auth), + User = proplists:get_value(username, BasicAuth), + Password = proplists:get_value(password, BasicAuth), + {security, + {authorization_basic, [#{username => User, password => Password}]}}.