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}]}}.