diff --git a/.gitignore b/.gitignore index 59466137b236a20278cab4543387ae18b785e8b3..2726ae3571ae774a2fd5e6e45dab716ac0d35699 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,3 @@ -.applist deps/ log/ **/*.java @@ -15,6 +14,8 @@ sample vcs.xml workspace.xml .idea/ +.DS_Store +_build # Excessive for iOS models apps/roster/priv/macbert/Model/PublishService.swift @@ -22,4 +23,24 @@ apps/roster/priv/macbert/Model/Whitelist.swift apps/roster/priv/macbert/Model/feed.swift apps/roster/priv/macbert/Spec/PublishService_Spec.swift apps/roster/priv/macbert/Spec/Whitelist_Spec.swift -apps/roster/priv/macbert/Spec/feed_Spec.swift \ No newline at end of file +apps/roster/priv/macbert/Spec/feed_Spec.swift + +.applist +0.3 +priv/protobuf/service_friend/Contact.proto +priv/protobuf/service_friend/Friend.proto +priv/protobuf/service_friend/Roster.proto +priv/protobuf/service_friend/contactStatus.proto +priv/protobuf/service_friend/friendStatus.proto +priv/protobuf/service_friend/muc.proto +priv/protobuf/service_friend/p2p.proto +priv/protobuf/service_friend/rosterStatus.proto +priv/protobuf/service_room/History.proto +priv/protobuf/service_room/Job.proto +priv/protobuf/service_room/Message.proto +priv/protobuf/service_room/act.proto +priv/protobuf/service_room/historyType.proto +priv/protobuf/service_room/jobType.proto +priv/protobuf/service_room/messageEvent.proto +priv/protobuf/service_room/messageStatus.proto +priv/protobuf/service_room/messageType.proto diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..7526b86f6813d4924fc3b222f90420834a826254 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,76 @@ +FROM erlang:20.2.2 + +RUN apt-get update && apt-get install -y \ + inotify-tools \ + gettext \ + sudo \ + curl \ + mc \ + && rm -rf /var/lib/apt/lists/* + +RUN curl -fsSL https://raw.github.com/synrc/mad/master/mad > mad \ + && chmod +x mad \ + && sudo cp mad /usr/bin/ + +# Add user +RUN useradd -m nynja && echo "nynja:77777" | chpasswd && adduser nynja sudo + + +# Remove host checking +RUN echo "Host \tStrictHostKeyChecking no\n" >> /etc/ssh/config + +USER nynja + +# make sure your domain is accepted +# Create known_hosts +RUN mkdir /home/nynja/.ssh/ +RUN touch /home/nynja/.ssh/known_hosts + +# Copy over private key, and set permissions +RUN chown -R nynja:nynja /home/nynja/.ssh +ADD files/id_rsa /home/nynja/.ssh/ +ADD files/id_rsa.pub /home/nynja/.ssh/ + +#RUN chmod 600 ~/.ssh/id_rsa && \ +# chmod 644 ~/.ssh/id_rsa.pub + +RUN eval $(ssh-agent) && \ + ssh-add ~/.ssh/id_rsa & >/dev/null + +# Add github key +RUN ssh-keyscan github.com >> /home/nynja/.ssh/known_hosts + +WORKDIR /home/nynja + + + +RUN git clone -b cluster ssh://git@github.com/NYNJA-MC/server.git +# + +WORKDIR /home/nynja/server + +RUN rm -f vm.args etc/emq.conf + +RUN mad dep +RUN mad cle comp pla + +CMD ["/home/nynja/server/k8s-start.sh"] + +#VOLUME ["/home/nynja/server/log", "/home/nynja/server/etc"] + +# emqttd will occupy these port: +# - 1883 port for MQTT +# - 8883 port for MQTT(SSL) +# - 8083 for WebSocket/HTTP +# - 8084 for WSS/HTTPS +# - 8080 for mgmt API +# - 18083 for dashboard +# - 4369 for port mapping +# - 6000-6999 for distributed node +EXPOSE 1883 8883 8083 8084 8080 8888 18083 4369 + + + + + + diff --git a/admin/main b/admin/main index 14cb64fadd843c94c4a5bef1a0d0b58735517aa1..581158a2795f6c61d7c688a6d26b440260dbeaed 100755 --- a/admin/main +++ b/admin/main @@ -12,12 +12,9 @@ NoColor='\033[0m' # Server IP addresses release_server_ip=35.156.90.43 -migration_server_ip=34.211.25.163 -spa_server_ip=54.68.81.217 dev_server_ip=54.201.154.141 -staging_server_ip=52.42.76.167 -loaddb_server_ip=35.163.197.32 +call_server_ip=34.220.151.7 default_server_ip=${dev_server_ip} @@ -62,29 +59,20 @@ function choose_server() { echo -e "Please, choose server. ${Yellow}1${NoColor} - Release Frankfurt Server (${release_server_ip}); -${Yellow}2${NoColor} - WEB SPA server (${spa_server_ip}); -${Yellow}3${NoColor} - Migration Server (${migration_server_ip}) -${Yellow}4${NoColor} - Dev server (${dev_server_ip}); -${Yellow}5${NoColor} - Staging server (${staging_server_ip}); -${Yellow}6${NoColor} - Load DB server (${loaddb_server_ip}): " +${Yellow}2${NoColor} - Dev Server (${dev_server_ip}); +${Yellow}3${NoColor} - Call SDK Server (${call_server_ip}): " read server_type - if [ -z ${server_type} ] || [ ${server_type} -lt 1 ] || [ ${server_type} -gt 6 ]; then + if [ -z ${server_type} ] || [ ${server_type} -lt 1 ] || [ ${server_type} -gt 3 ]; then separator echo -e "Invalid server_type value. Use ${LightGreen}${default_server_ip}${NoColor} server by default." fi if [ ! -z ${server_type} ]; then if [ ${server_type} == 2 ]; then - working_ip=${spa_server_ip} - elif [ ${server_type} == 3 ]; then - working_ip=${migration_server_ip} - elif [ ${server_type} == 4 ]; then working_ip=${dev_server_ip} - elif [ ${server_type} == 5 ]; then - working_ip=${staging_server_ip} - elif [ ${server_type} == 6 ]; then - working_ip=${loaddb_server_ip} + elif [ ${server_type} == 3 ]; then + working_ip=${call_server_ip} elif [ ${server_type} == 1 ]; then working_ip=${release_server_ip} path_to_key=${frankfurt_pem_key} diff --git a/apps/roster/include/micro.hrl b/apps/roster/include/micro.hrl new file mode 100644 index 0000000000000000000000000000000000000000..40f10cba50e1b31d3f23358b4ef47ba3d8c42486 --- /dev/null +++ b/apps/roster/include/micro.hrl @@ -0,0 +1,12 @@ +-ifndef(MICRO_HRL). +-define(MICRO_HRL, true). + +-define(SYS_BRIDGE_TEST_CLIENT, <<"sys_micro_bridge_test">>). + +-record('LinkRoster', {id = [] :: binary(), + phone_id = [] :: binary()}). + +-record('LinkProfile', {id = [] :: binary(), + phone = [] :: binary()}). + +-endif. \ No newline at end of file diff --git a/apps/roster/include/rest_static.hrl b/apps/roster/include/rest_static.hrl index e0449682f7dba5d6618445629d0641d82f2fef74..dcc94d45d4f366049822715fd8d38187f10ac6a6 100644 --- a/apps/roster/include/rest_static.hrl +++ b/apps/roster/include/rest_static.hrl @@ -15,4 +15,21 @@ -define(FOUND_ROOM_AVATAR, <<"avatar">>). -define(FOUND_ROOM_DESCRIPTION, <<"description">>). +-define(AWS_ACCESS_KEY_ID, proplists:get_value(access_key_id, application:get_env(roster, amazon_api, []))). +-define(AWS_SECRET_ACCESS_KEY, proplists:get_value(secret_access_key, application:get_env(roster, amazon_api, []))). + + +%% TODO replace it to separate hrl module +-record('CallBubbleJSON', { + feed_id = [] :: [] | binary(), + call_id = [] :: [] | binary(), + type = [] :: [] | binary(), + recipients = [] :: [] | binary(), + duration = 0 :: [] | integer(), + start_time = 0 :: [] | integer(), + status = [] :: [] | binary(), + content_type = [] :: [] | binary(), + ended_by = [] :: [] | binary(), + count = 0 :: [] | integer()}). + -endif. \ No newline at end of file diff --git a/apps/roster/include/roster.hrl b/apps/roster/include/roster.hrl index da498bb3a98fd439a86e26db5dd534b6701f113e..435a06db99f3b1a3212e1ab34644c5aef8593449 100644 --- a/apps/roster/include/roster.hrl +++ b/apps/roster/include/roster.hrl @@ -1,20 +1,23 @@ - -ifndef(ROSTER_HRL). -define(ROSTER_HRL, true). -include_lib("kvs/include/feed.hrl"). -include_lib("kvs/include/kvs.hrl"). -include_lib("bpe/include/bpe.hrl"). +-define(SYS_BPE_CLIENT, <<"sys_bpe">>). +-define(SYS_REST_CLIENT, <<"sys_rest">>). + -define(HIST_LIMIT, 1000). -define(MAX_UNREAD, 100). +-define(MAX_PROFILE_OBJS, 5). -define(MAX_LAST_ROOMS, 5). -define(RECENT_ITEMS_COUNT, 5). - --define(VERSION, <<"version/10">>). +-define(VERSION, "11"). %% #'Feature'{} group -define(FILE_GROUP, <<"FILE_DATA">>). -define(LANG_GROUP, <<"LANGUAGE_SETTINGS">>). +-define(DUMMY_GROUP, <<"DUMMY_GROUP">>). %% #'Feature'{} file group keys -define(SIZE_KEY , <<"SIZE">>). @@ -44,6 +47,7 @@ -define(GEO_URL , "http://geolite.maxmind.com/download/geoip/database/GeoLite2-City.tar.gz"). -define(AUTH_GROUP , <<"AUTH_DATA">>). + -record(chain, {?CONTAINER, aclver = [], unread = {[],[]}}). -record(push, {model = [] :: [] | term(), @@ -53,17 +57,17 @@ badge = [] :: [] | integer(), sound = [] :: [] | binary()}). --record('Search', {id = [] :: [] | integer(), - ref = [] :: [] | binary(), - field = [] :: [] | binary(), - type = [] :: [] | '==' | '!=' | 'like', - value = [] :: [] | term(), - status = [] :: [] | profile | roster | contact | member | room }). +-record('Search', {id = [] :: integer(), + ref = [] :: binary(), + field = [] :: binary(), + type = [] :: '==' | '!=' | 'like', + value = [] :: list(term()), + status = [] :: profile | roster | contact | member | room }). --record(p2p, {from = [] :: [] | binary(), - to = [] :: [] | binary() }). +-record(p2p, {from = [] :: binary(), + to = [] :: binary() }). --record(muc, {name = [] :: [] | binary() }). +-record(muc, {name = [] :: binary() }). %% member query info -record(mqi, {feed_id = [] :: #muc{}, @@ -71,21 +75,21 @@ status = [] :: [] | admin | member | removed}). %% [] - all members, admin - admins and owners -record('Feature', {id = [] :: [] | binary(), - key = [] :: [] | binary(), - value = [] :: [] | binary(), - group = [] :: [] | binary()}). + key = [] :: binary(), + value = <<>> :: binary(), + group = ?DUMMY_GROUP :: binary()}). -record('Service', {id = [] :: [] | binary(), - type = [] :: [] | email | vox | aws | wallet, + type = [] :: email | vox | aws | wallet, data = [] :: term(), login = [] :: [] | binary(), password = [] :: [] | binary(), expiration = [] :: [] | integer(), - status = [] :: [] | verified | added}). + status = [] :: [] | verified | added | add | remove}). -record('Member', {id = [] :: [] | integer(), - container = chain :: chain | cur, - feed_id = [] :: #muc{} | #p2p{}, + container = chain :: chain | cur | [], + feed_id = [] :: #muc{} | #p2p{} | [] , prev = [] :: [] | integer(), next = [] :: [] | integer(), feeds = [] :: list(), @@ -96,8 +100,8 @@ alias = [] :: [] | binary(), reader = 0 :: [] | integer(), update = 0 :: [] | integer(), - settings = [] :: list(#'Feature'{}), - services = [] :: list(#'Service'{}), + settings = [] :: [] | list(#'Feature'{}), + services = [] :: [] | list(#'Service'{}), presence = [] :: [] | online | offline, status = [] :: [] | admin | member | removed | patch | owner }). @@ -118,7 +122,7 @@ downloaded = 0 :: integer()}). -record('Message', {id = [] :: [] | integer(), - container = chain :: chain | cur, + container = chain :: chain | cur | [], feed_id = [] :: #muc{} | #p2p{}, prev = [] :: [] | integer(), next = [] :: [] | integer(), @@ -127,18 +131,17 @@ to = [] :: [] | binary(), created = [] :: [] | integer(), files = [] :: list(#'Desc'{}), - type = [] :: [] | [sys | reply | forward | read | edited | cursor], + type = [] :: [sys | reply | forward | read | edited | cursor ], link = [] :: [] | integer() | #'Message'{}, - seenby = [] :: list(binary() | integer()), - repliedby = [] :: list(integer()), - mentioned = [] :: list(integer()), + seenby = [] :: [] | list(binary() | integer()), + repliedby = [] :: [] | list(integer()), + mentioned = [] :: [] | list(integer()), status = [] :: [] | async | delete | clear| update | edit}). --record('Link', {id = [] :: [] | binary(), %% composite id, ChannelId_LinkId - name = [] :: [] | binary(), %% actually the link - room_id = [] :: [] | binary(), %% parent Room id - created = 0 :: [] | integer(), %% datetime field for sorting - status = [] :: [] | gen | check | add | delete | update }). +-record('Link', {id = [] :: [] | binary(), %% actually the link + name = [] :: [] | binary(), %% Room id etc + type = [] :: [] | group | channel, + status = [] :: [] | get | join | update | delete }). -record('Room', {id = [] :: [] | binary(), name = [] :: [] | binary(), @@ -154,11 +157,11 @@ unread = 0 :: [] | integer(), mentions = [] :: list(integer()), readers = [] :: list(integer()), - last_msg = [] :: [] | #'Message'{}, + last_msg = [] :: [] | integer() | #'Message'{}, update = 0 :: [] | integer(), created = 0 :: [] | integer(), - status = [] :: [] | create | leave| add | remove - | patch | get | delete | last_msg}). + status = [] :: [] | create | leave| add | remove | removed | join | info + | patch | get | delete | last_msg | mute | unmute}). -record('Tag', {roster_id = [] :: [] | binary(), name = [] :: binary(), @@ -180,43 +183,43 @@ names = [] :: [] | binary(), surnames = [] :: [] | binary(), nick = [] :: [] | binary(), - reader = [] :: [] | list(integer()), + reader = [] :: [] | integer() | list(integer()), unread = 0 :: [] | integer(), last_msg = [] :: [] | #'Message'{}, update = 0 :: [] | integer(), created = 0 :: [] | integer(), settings = [] :: list(#'Feature'{}), services = [] :: list(#'Service'{}), - presence = [] :: [] | online | offline, + presence = [] :: [] | online | offline | binary(), status = [] :: [] | request | authorization | ignore | internal | friend | last_msg | ban | banned | deleted }). --record('ExtendedStar', {star = [] :: #'Star'{}, - from = [] :: #'Contact'{} | #'Room'{}}). +-record('ExtendedStar', {star = [] :: #'Star'{} | [], + from = [] :: #'Contact'{} | #'Room'{} | []}). -record('Auth', {client_id = [] :: [] | binary(), dev_key = [] :: [] | binary(), user_id = [] :: [] | binary(), - phone = [] :: [] | binary(), + phone = [] :: [] | binary() | {fake, binary()}, token = [] :: [] | binary(), type = [] :: [] | atom(), sms_code = [] :: [] | binary(), attempts = [] :: [] | integer(), - services = [] :: list(atom()), - settings = [] :: list(#'Feature'{}), + services = [] :: list(term()), + settings = [] :: [] | list(#'Feature'{}), push = [] :: [] | binary(), os = [] :: [] | ios | android | web, created = [] :: [] | integer(), last_online = [] :: [] | integer() }). --record('Roster', {id = [] :: [] | integer(), +-record('Roster', {id = [] :: [] | binary() | integer(), names = [] :: [] | binary(), surnames = [] :: [] | binary(), email = [] :: [] | binary(), nick = [] :: [] | binary(), - userlist = [] :: list(#'Contact'{}), + userlist = [] :: list(#'Contact'{} | integer()), roomlist = [] :: list(#'Room'{}), - favorite = [] :: list(#'ExtendedStar'{}), + favorite = [] :: list(#'Star'{}), tags = [] :: list(#'Tag'{}), phone = [] :: [] | binary(), avatar = [] :: [] | binary(), @@ -225,40 +228,44 @@ | add | update | list | patch | last_msg }). -record('Profile', {phone = [] :: [] | binary(), - services = [] :: list(#'Service'{}), - rosters = [] :: list(#'Roster'{}), - settings = [] :: list(#'Feature'{}), + services = [] :: [] | list(#'Service'{}), + rosters = [] :: [] | list(#'Roster'{}), + settings = [] :: [] | list(#'Feature'{}), update = 0 :: integer(), balance = 0 :: integer(), - presence = [] :: [] | offline | online, - status = [] :: [] | remove | get | patch}). + presence = [] :: [] | offline | online | binary(), + status = [] :: [] | remove | get | patch | update| delete }). --record('Friend', {phone_id = [] :: [] | binary(), - friend_id = [] :: [] | binary(), - settings = [] :: list(#'Feature'{}), - status = [] :: [] | ban | unban | request | confirm | update | ignore}). +-record('Presence', {uid = <<>> :: binary(), + status = [] :: [] | offline | online | binary()}). --record(act, {name= <<"publish">> :: [] | binary(), data=[]:: binary() | integer() | list(term())}). +-record('Friend', {phone_id = [] :: binary(), + friend_id = [] :: binary(), + settings = [] :: [] | list(#'Feature'{}), + status = [] :: ban | unban | request | confirm | update | ignore}). + +-record(act, {name= <<"publish">> :: [] | binary(), data=[]:: binary() | integer() | list(term())}). -record('Job', {id = [] :: [] | integer(), - container = chain :: chain, + container = chain :: chain | [], feed_id = [] :: #act{}, prev = [] :: [] | integer(), next = [] :: [] | integer(), context = [] :: [] | integer() | binary(), proc = [] :: [] | integer() | #process{}, - time = [] :: integer(), - data = [] :: list( #'Message'{}), - events = [] :: list(#messageEvent{}), - settings = [] :: list(#'Feature'{}), + time = [] :: [] | integer(), + data = [] :: list(#'Message'{}), + events = [] :: [] | list(#messageEvent{}), + settings = [] :: [] | list(#'Feature'{}), status = [] :: [] | init | update | delete | pending | stop | complete}). --record('History', {roster_id = [] :: [] | binary(), - feed = [] :: [] | #p2p{} | #muc{} | #act{} | #'StickerPack'{}, +-record('History', {roster_id = [] :: binary(), + feed = [] :: #p2p{} | #muc{} | #act{} | #'StickerPack'{} | [], size = 0 :: [] | integer(), entity_id = 0 :: [] | integer(), - data = [] :: list(#'Message'{} | #'Job'{} | #'StickerPack'{}), - status = [] :: [] | updated | get | update | last_loaded | last_msg | get_reply}). + data = [] :: [] | list(#'Message'{} | #'Job'{} | #'StickerPack'{}), + status = [] :: updated | get | update | last_loaded | last_msg | get_reply | double_get | delete | + image | video | file | link | audio | contact | location | text}). -record('Schedule', {id = [] :: [] | integer(), @@ -273,7 +280,7 @@ %% Nick | {nick, <<"NickBinary">>} | [Roster.id] %% Link | {link, <<"LinkBinary">>} | [Room.id] -record('Index', {id = [] :: [] | term(), - roster = [] :: list(integer())}). + roster = [] :: list(term())}). -record('Whitelist', {phone = [] :: [] | binary(), created = 0 :: [] | integer()}). @@ -287,6 +294,9 @@ -record(io, {code = [] :: [] | #ok{} | #error{} | #ok2{} | #error2{}, data = <<>> :: [] | <<>> | #'Roster'{} | { atom(), binary() | integer() }}). +-record('Ack', {table = 'Message' :: atom(), + id = [] :: [] | binary()}). + %% Server response with error %% code - list of binary status codes %% data - requested erlang model diff --git a/apps/roster/include/roster_test.hrl b/apps/roster/include/roster_test.hrl index 31e5fec448c3a6305e0279cb58a268932b5e7bf1..151cb3170be3a479bd722199fc066aa5e8eeac30 100644 --- a/apps/roster/include/roster_test.hrl +++ b/apps/roster/include/roster_test.hrl @@ -4,7 +4,7 @@ -define(LOC, "127.0.0.1"). -define(HOST, ?LOC). -define(DRP_TMOUT, case ?HOST of ?LOC -> 100;_ -> 1000 end). --define(TIMEOUT, 10000). +-define(TIMEOUT, 7000). %% Auth data %% FG - Feature Group @@ -14,5 +14,9 @@ -define(FAKE_NUMBER_PREFIX, <<"380">>). -define(DEV_KEY_PREFIX, <<"DevKey_">>). + +-record(state, {mqttc = [], mqtt_opts = [], client_id = <<>>, username = <<"api">>, + receive_pid = [], filter = filter, last_res = []}). + -endif. diff --git a/apps/roster/static/main_text.hrl b/apps/roster/include/static/main_text.hrl similarity index 100% rename from apps/roster/static/main_text.hrl rename to apps/roster/include/static/main_text.hrl diff --git a/apps/roster/include/static/main_var.hrl b/apps/roster/include/static/main_var.hrl new file mode 100644 index 0000000000000000000000000000000000000000..4efb9a72cfffac68d22d5ec9724105b528c91cc0 --- /dev/null +++ b/apps/roster/include/static/main_var.hrl @@ -0,0 +1,17 @@ +%% ------------------------------------------------------------------ +%% Variables common for multiple modules +%% ------------------------------------------------------------------ + +-define(SYS_MSG_TYPE, [sys]). + +%% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +%% Atom Statuses +%% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +-define(CREATE_STATUS, create). +-define(ADD_STATUS, add). +-define(DELETE_STATUS, delete). +-define(REMOVE_STATUS, remove). +-define(JOIN_STATUS, join). + +-define(VIDEO_THUMBNAIL_DEFAULT, <<"https://s3-us-west-2.amazonaws.com/nynja-defaults/ic_video_without_thumbnail.png">>). \ No newline at end of file diff --git a/apps/roster/static/prometheus_text.hrl b/apps/roster/include/static/prometheus_text.hrl similarity index 100% rename from apps/roster/static/prometheus_text.hrl rename to apps/roster/include/static/prometheus_text.hrl diff --git a/apps/roster/static/prometheus_var.hrl b/apps/roster/include/static/prometheus_var.hrl similarity index 100% rename from apps/roster/static/prometheus_var.hrl rename to apps/roster/include/static/prometheus_var.hrl diff --git a/apps/roster/static/push_notification_var.hrl b/apps/roster/include/static/push_notification_var.hrl similarity index 100% rename from apps/roster/static/push_notification_var.hrl rename to apps/roster/include/static/push_notification_var.hrl diff --git a/apps/roster/include/static/rest_text.hrl b/apps/roster/include/static/rest_text.hrl new file mode 100644 index 0000000000000000000000000000000000000000..692e23675de19fc5c4f2773e9bc21cd01b20c6f4 --- /dev/null +++ b/apps/roster/include/static/rest_text.hrl @@ -0,0 +1,47 @@ +%% ------------------------------------------------------------------ +%% Errors and info messages for rest modules +%% ------------------------------------------------------------------ + +-define(TEST_API_ENDPOINT, "/test"). + +-define(RESPONSE_200, <<"Success">>). + +-define(ERROR_400, <<"Bad Request">>). +-define(ERROR_401, <<"Unauthorized">>). +-define(ERROR_403, <<"Forbidden">>). +-define(ERROR_404, <<"Not Found">>). +-define(ERROR_405, <<"Method Not Allowed">>). +-define(ERROR_INVALID_JSON, <<"Invalid Request Json">>). +-define(ERROR_MISSING_PARAM, <<"Missing Request Parameter(s)">>). +-define(ERROR_INVALID_REQUEST_PARAM, <<"Invalid Request Parameter(s)">>). +-define(ERROR_MISSING_HEADER, <<"Missing Request Header(s)">>). +-define(ERROR_PERMISSION_DENIED, <<"Permission Denied">>). + +%% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +%% Rest Publish Module +%% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +-define(ERROR_INVALID_QOS_PARAM, <<"Invalid 'qos' Param. Valid Values: 0, 1, 2">>). +-define(ERROR_INVALID_MESSAGE_PARAM, <<"Invalid 'message' Param. Should be base64 encoded bytearray">>). + +%% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +%% Call Room Interface Module +%% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +-define(ERROR_USER_404, <<"User Not Found">>). +-define(IS_MEMBER_FLAG, <<"is_member">>). +-define(ROOM_TYPE_VALUE, <<"type">>). + +-define(CALL_TYPE_P2P, <<"p2p">>). +-define(CALL_TYPE_CONFERENCE, <<"conference">>). + +-define(CONTENT_TYPE_VIDEOCALL, <<"videoCall">>). +-define(CONTENT_TYPE_AUDIOCALL, <<"audioCall">>). + +%% feature object for call bubbles +-define(CALL_FG_CALL_DATA, <<"CALL_DATA">>). +-define(CALL_FK_DURATION, <<"DURATION">>). +-define(CALL_FK_START_TIME, <<"START_TIME">>). +-define(CALL_FK_CONFERENCE_ID, <<"CONFERENCE_ID">>). +-define(CALL_FK_ENDED_BY, <<"ENDED_BY">>). +-define(CALL_FK_COUNT, <<"COUNT">>). \ No newline at end of file diff --git a/apps/roster/include/static/rest_var.hrl b/apps/roster/include/static/rest_var.hrl new file mode 100644 index 0000000000000000000000000000000000000000..2c13ed749a0e10fabd6c4cb04108a06f30502f4a --- /dev/null +++ b/apps/roster/include/static/rest_var.hrl @@ -0,0 +1,45 @@ +%% ------------------------------------------------------------------ +%% 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/static/room_channel_text.hrl b/apps/roster/include/static/roster_text.hrl similarity index 92% rename from apps/roster/static/room_channel_text.hrl rename to apps/roster/include/static/roster_text.hrl index 309aec9141b061bbf5e391363eb986a9fdda7c9f..1e21dd1909d552380bef9e01485ab793a46cd25a 100644 --- a/apps/roster/static/room_channel_text.hrl +++ b/apps/roster/include/static/roster_text.hrl @@ -3,12 +3,14 @@ %% ------------------------------------------------------------------ %% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -%% Common Group and Channel stuff +%% Room stuff (Common for Group and Channel) %% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -define(SYS_MSG_UPDATE_ROOM_NAME, <<" is renamed to">>). -define(SYS_MSG_UPDATE_ROOM_AVATAR, <<" avatar is updated">>). +-define(ERROR_ROOM_NOT_FOUND, <<"Room not found">>). + %% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - %% Group stuff diff --git a/apps/roster/static/room_channel_var.hrl b/apps/roster/include/static/roster_var.hrl similarity index 93% rename from apps/roster/static/room_channel_var.hrl rename to apps/roster/include/static/roster_var.hrl index 5dc7a107f7ebb49f3131ef718e2a210b07a63e61..b2862dd29803e4d91968f122a20bb9f195383f9d 100644 --- a/apps/roster/static/room_channel_var.hrl +++ b/apps/roster/include/static/roster_var.hrl @@ -1,12 +1,14 @@ %% ------------------------------------------------------------------ -%% Static Variables for Room and Channel modules +%% Static Variables for modules %% ------------------------------------------------------------------ - +-define(LINK_URL, "https://link.nynja.net/"). %% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - %% Room stuff %% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +-define(CALL_ROOM, call). + %% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - %% Channel stuff %% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/apps/roster/priv/macbert/Model/Ack.swift b/apps/roster/priv/macbert/Model/Ack.swift new file mode 100644 index 0000000000000000000000000000000000000000..d5706c4af599455eb9d19c790ce86b94647f1355 --- /dev/null +++ b/apps/roster/priv/macbert/Model/Ack.swift @@ -0,0 +1,5 @@ + +class Ack { + var table: StringAtom? + var id: String? +} \ No newline at end of file diff --git a/apps/roster/priv/macbert/Model/Auth.swift b/apps/roster/priv/macbert/Model/Auth.swift index a991f6ef71a0ebeceb631044b8d7718851284256..2352012a18f796494e3cf364274d223aa09c780c 100644 --- a/apps/roster/priv/macbert/Model/Auth.swift +++ b/apps/roster/priv/macbert/Model/Auth.swift @@ -3,12 +3,12 @@ class Auth { var client_id: String? var dev_key: String? var user_id: String? - var phone: String? + var phone: AnyObject? var token: String? var type: StringAtom? var sms_code: String? var attempts: Int64? - var services: [String]? + var services: [AnyObject]? var settings: [Feature]? var push: String? var os: AnyObject? diff --git a/apps/roster/priv/macbert/Model/Contact.swift b/apps/roster/priv/macbert/Model/Contact.swift index f4d7f03ecebd91378d93194d1b858f50f54d276b..89af7d9d36a89fdfa47cfba5f507f4a60abed566 100644 --- a/apps/roster/priv/macbert/Model/Contact.swift +++ b/apps/roster/priv/macbert/Model/Contact.swift @@ -5,7 +5,7 @@ class Contact { var names: String? var surnames: String? var nick: String? - var reader: [AnyObject]? + var reader: AnyObject? var unread: Int64? var last_msg: Message? var update: Int64? diff --git a/apps/roster/priv/macbert/Model/Job.swift b/apps/roster/priv/macbert/Model/Job.swift index c6462e44d1ce5ba8597f64b5864f7a58befc765f..e588822cd3900b1a83cabcbf33ea8e72315d7c82 100644 --- a/apps/roster/priv/macbert/Model/Job.swift +++ b/apps/roster/priv/macbert/Model/Job.swift @@ -1,6 +1,7 @@ class Job { var id: Int64? + var container: AnyObject? var feed_id: act? var prev: Int64? var next: Int64? diff --git a/apps/roster/priv/macbert/Model/Link.swift b/apps/roster/priv/macbert/Model/Link.swift index ced83eb4de2d45d6fe82c7707c84fe3e5cb5e9df..e7798fa21e331d30ce5604d893f08da6ad35bded 100644 --- a/apps/roster/priv/macbert/Model/Link.swift +++ b/apps/roster/priv/macbert/Model/Link.swift @@ -2,7 +2,6 @@ class Link { var id: String? var name: String? - var room_id: String? - var created: Int64? + var type: AnyObject? var status: AnyObject? } \ No newline at end of file diff --git a/apps/roster/priv/macbert/Model/Room.swift b/apps/roster/priv/macbert/Model/Room.swift index 9f42ccfb49e12915c35647e999f8326702e1e6df..5ecb7eed3c4b2c55580607e6e00104023a763bdd 100644 --- a/apps/roster/priv/macbert/Model/Room.swift +++ b/apps/roster/priv/macbert/Model/Room.swift @@ -14,7 +14,7 @@ class Room { var unread: Int64? var mentions: [AnyObject]? var readers: [AnyObject]? - var last_msg: Message? + var last_msg: AnyObject? var update: Int64? var created: Int64? var status: AnyObject? diff --git a/apps/roster/priv/macbert/Model/Roster.swift b/apps/roster/priv/macbert/Model/Roster.swift index 52989b7492216f42215615eb2a9ec1a1cfbc72c4..d9ff48490012ddced1d8e209165f77b38e06bc82 100644 --- a/apps/roster/priv/macbert/Model/Roster.swift +++ b/apps/roster/priv/macbert/Model/Roster.swift @@ -1,13 +1,13 @@ class Roster { - var id: Int64? + var id: AnyObject? var names: String? var surnames: String? var email: String? var nick: String? - var userlist: [Contact]? + var userlist: [AnyObject]? var roomlist: [Room]? - var favorite: [ExtendedStar]? + var favorite: [Star]? var tags: [Tag]? var phone: String? var avatar: String? diff --git a/apps/roster/priv/macbert/Model/Search.swift b/apps/roster/priv/macbert/Model/Search.swift index c4e9da9896ca4f3975d38f55d977032dc57fe279..daeafd5c6afba2ad34555d2680518ffe57458def 100644 --- a/apps/roster/priv/macbert/Model/Search.swift +++ b/apps/roster/priv/macbert/Model/Search.swift @@ -4,6 +4,6 @@ class Search { var ref: String? var field: String? var type: AnyObject? - var value: AnyObject? + var value: [AnyObject]? var status: AnyObject? } \ No newline at end of file diff --git a/apps/roster/priv/macbert/Model/beginEvent.swift b/apps/roster/priv/macbert/Model/beginEvent.swift index 02e3082208025873097e7967230047d8865a661d..af7d4531b927e95955ca35797437ff6f7baef214 100644 --- a/apps/roster/priv/macbert/Model/beginEvent.swift +++ b/apps/roster/priv/macbert/Model/beginEvent.swift @@ -2,4 +2,5 @@ class beginEvent { var name: StringAtom? var module: StringAtom? + var prompt: [AnyObject]? } \ No newline at end of file diff --git a/apps/roster/priv/macbert/Model/boundaryEvent.swift b/apps/roster/priv/macbert/Model/boundaryEvent.swift index ddbf3d427df25ac36c1d317e598ac070e3698586..006afe0fbc2a3f96211e0518ba042edffde66824 100644 --- a/apps/roster/priv/macbert/Model/boundaryEvent.swift +++ b/apps/roster/priv/macbert/Model/boundaryEvent.swift @@ -1,10 +1,11 @@ class boundaryEvent { var name: StringAtom? + var module: StringAtom? + var prompt: [AnyObject]? var payload: String? var timeout: [AnyObject]? var timeDate: String? var timeDuration: String? var timeCycle: String? - var module: StringAtom? } \ No newline at end of file diff --git a/apps/roster/priv/macbert/Model/endEvent.swift b/apps/roster/priv/macbert/Model/endEvent.swift index f3fcc76b821cefb63fd4d9d951480d490b6caba7..7e0f2b03eefdadec595db419586e723d5fbb9d5d 100644 --- a/apps/roster/priv/macbert/Model/endEvent.swift +++ b/apps/roster/priv/macbert/Model/endEvent.swift @@ -2,4 +2,5 @@ class endEvent { var name: StringAtom? var module: StringAtom? + var prompt: [AnyObject]? } \ No newline at end of file diff --git a/apps/roster/priv/macbert/Model/hist.swift b/apps/roster/priv/macbert/Model/hist.swift index 0fe22a4cd4a0710db62e070996a32de89fa8b0f3..f46c4d04da34c4202bef75aacd9fefe81c250acb 100644 --- a/apps/roster/priv/macbert/Model/hist.swift +++ b/apps/roster/priv/macbert/Model/hist.swift @@ -8,5 +8,6 @@ class hist { var feeds: [AnyObject]? var name: String? var task: StringAtom? + var docs: [AnyObject]? var time: AnyObject? } \ No newline at end of file diff --git a/apps/roster/priv/macbert/Model/messageEvent.swift b/apps/roster/priv/macbert/Model/messageEvent.swift index 75278a62c6a75ea818fc70e8c533fe33b75d06a2..6dc3793443066623ada6623e3b5aa617af25e01f 100644 --- a/apps/roster/priv/macbert/Model/messageEvent.swift +++ b/apps/roster/priv/macbert/Model/messageEvent.swift @@ -1,6 +1,8 @@ class messageEvent { var name: StringAtom? + var module: StringAtom? + var prompt: [AnyObject]? var payload: String? var timeout: [AnyObject]? } \ No newline at end of file diff --git a/apps/roster/priv/macbert/Model/process.swift b/apps/roster/priv/macbert/Model/process.swift index 5b592929b4b16a5bc1f93ba8030dda6fb29c4a68..0069d0df142f5b2c523cbc5f0c4f6c0ec491b8f2 100644 --- a/apps/roster/priv/macbert/Model/process.swift +++ b/apps/roster/priv/macbert/Model/process.swift @@ -19,7 +19,7 @@ class process { var timer: String? var notifications: AnyObject? var result: String? - var started: String? + var started: [AnyObject]? var beginEvent: StringAtom? var endEvent: StringAtom? } \ No newline at end of file diff --git a/apps/roster/priv/macbert/Model/receiveTask.swift b/apps/roster/priv/macbert/Model/receiveTask.swift index d955456d15052271dde43568938790446f2a43f1..bdb436a8e596c213c7fccfd4a6dca1ff7f0c871b 100644 --- a/apps/roster/priv/macbert/Model/receiveTask.swift +++ b/apps/roster/priv/macbert/Model/receiveTask.swift @@ -1,6 +1,7 @@ class receiveTask { var name: StringAtom? - var roles: String? var module: StringAtom? + var prompt: [AnyObject]? + var roles: String? } \ No newline at end of file diff --git a/apps/roster/priv/macbert/Model/sequenceFlow.swift b/apps/roster/priv/macbert/Model/sequenceFlow.swift index e3951a1e71670cd5d4e21a154e62e360d20c68f7..2a0e73d3bb04b43e25c018807bba3b3cd1e4f2ae 100644 --- a/apps/roster/priv/macbert/Model/sequenceFlow.swift +++ b/apps/roster/priv/macbert/Model/sequenceFlow.swift @@ -1,5 +1,5 @@ class sequenceFlow { var source: StringAtom? - var target: StringAtom? + var target: AnyObject? } \ No newline at end of file diff --git a/apps/roster/priv/macbert/Model/serviceTask.swift b/apps/roster/priv/macbert/Model/serviceTask.swift index 1942b21b032b511a4553f3a91074a2d70a84a15c..6927e6da445ff90b66dce59c1b1b4d0ddbd840e2 100644 --- a/apps/roster/priv/macbert/Model/serviceTask.swift +++ b/apps/roster/priv/macbert/Model/serviceTask.swift @@ -1,6 +1,7 @@ class serviceTask { var name: StringAtom? - var roles: String? var module: StringAtom? + var prompt: [AnyObject]? + var roles: String? } \ No newline at end of file diff --git a/apps/roster/priv/macbert/Model/task.swift b/apps/roster/priv/macbert/Model/task.swift index fd62e955f79affa63373e030beb9cdc349232661..b14868fa77c253ac2641ea11d349359cb52b6f3a 100644 --- a/apps/roster/priv/macbert/Model/task.swift +++ b/apps/roster/priv/macbert/Model/task.swift @@ -1,6 +1,7 @@ class task { var name: StringAtom? - var roles: String? var module: StringAtom? + var prompt: [AnyObject]? + var roles: String? } \ No newline at end of file diff --git a/apps/roster/priv/macbert/Model/timeoutEvent.swift b/apps/roster/priv/macbert/Model/timeoutEvent.swift index 6d11f83e6a0a94d95a34451f51ab0e73f9c5dd8c..422e1c9bd079feb755c23e631929d35307d3845f 100644 --- a/apps/roster/priv/macbert/Model/timeoutEvent.swift +++ b/apps/roster/priv/macbert/Model/timeoutEvent.swift @@ -1,10 +1,11 @@ class timeoutEvent { var name: StringAtom? + var module: StringAtom? + var prompt: [AnyObject]? var payload: String? var timeout: [AnyObject]? var timeDate: String? var timeDuration: String? var timeCycle: String? - var module: StringAtom? } \ No newline at end of file diff --git a/apps/roster/priv/macbert/Model/userTask.swift b/apps/roster/priv/macbert/Model/userTask.swift index faf1276ec3c85b9dc5a12abfeac9769341305ec7..ba5eb207eda371d28c2b08ab3ce4647d9c1e2173 100644 --- a/apps/roster/priv/macbert/Model/userTask.swift +++ b/apps/roster/priv/macbert/Model/userTask.swift @@ -1,6 +1,7 @@ class userTask { var name: StringAtom? - var roles: String? var module: StringAtom? + var prompt: [AnyObject]? + var roles: String? } \ No newline at end of file diff --git a/apps/roster/priv/macbert/Source/Decoder.swift b/apps/roster/priv/macbert/Source/Decoder.swift index ba1a753923388e49633c52a3ac27ea9e43d6b9f1..d0eb85fdeddd770a9a37511594928440c6d72874 100644 --- a/apps/roster/priv/macbert/Source/Decoder.swift +++ b/apps/roster/priv/macbert/Source/Decoder.swift @@ -1,6 +1,246 @@ func parseObject(name: String, body:[Model], tuple: BertTuple) -> AnyObject? { switch name { + case "writer": + if body.count != 5 { return nil } + let a_writer = writer() + a_writer.id = body[0].parse(bert: tuple.elements[1]) as? AnyObject + a_writer.count = body[1].parse(bert: tuple.elements[2]) as? Int64 + a_writer.cache = body[2].parse(bert: tuple.elements[3]) as? [AnyObject] + a_writer.args = body[3].parse(bert: tuple.elements[4]) as? AnyObject + a_writer.first = body[4].parse(bert: tuple.elements[5]) as? [AnyObject] + return a_writer + case "reader": + if body.count != 6 { return nil } + let a_reader = reader() + a_reader.id = body[0].parse(bert: tuple.elements[1]) as? AnyObject + a_reader.pos = body[1].parse(bert: tuple.elements[2]) as? Int64 + a_reader.cache = body[2].parse(bert: tuple.elements[3]) as? Int64 + a_reader.args = body[3].parse(bert: tuple.elements[4]) as? AnyObject + a_reader.feed = body[4].parse(bert: tuple.elements[5]) as? AnyObject + a_reader.dir = body[5].parse(bert: tuple.elements[6]) as? AnyObject + return a_reader + case "cur": + if body.count != 11 { return nil } + let a_cur = cur() + a_cur.id = body[0].parse(bert: tuple.elements[1]) as? AnyObject + a_cur.top = body[1].parse(bert: tuple.elements[2]) as? Int64 + a_cur.bot = body[2].parse(bert: tuple.elements[3]) as? Int64 + a_cur.dir = body[3].parse(bert: tuple.elements[4]) as? AnyObject + a_cur.reader = body[4].parse(bert: tuple.elements[5]) as? [AnyObject] + a_cur.writer = body[5].parse(bert: tuple.elements[6]) as? [AnyObject] + a_cur.args = body[8].parse(bert: tuple.elements[9]) as? [AnyObject] + return a_cur + case "iter": + if body.count != 5 { return nil } + let a_iter = iter() + a_iter.id = body[0].parse(bert: tuple.elements[1]) as? Int64 + a_iter.container = body[1].parse(bert: tuple.elements[2]) as? StringAtom + a_iter.feed = body[2].parse(bert: tuple.elements[3]) as? AnyObject + a_iter.next = body[3].parse(bert: tuple.elements[4]) as? Int64 + a_iter.prev = body[4].parse(bert: tuple.elements[5]) as? Int64 + return a_iter + case "container": + if body.count != 4 { return nil } + let a_container = container() + a_container.id = body[0].parse(bert: tuple.elements[1]) as? Int64 + a_container.top = body[1].parse(bert: tuple.elements[2]) as? Int64 + a_container.rear = body[2].parse(bert: tuple.elements[3]) as? Int64 + a_container.count = body[3].parse(bert: tuple.elements[4]) as? Int64 + return a_container + case "iterator": + if body.count != 6 { return nil } + let a_iterator = iterator() + a_iterator.id = body[0].parse(bert: tuple.elements[1]) as? Int64 + a_iterator.container = body[1].parse(bert: tuple.elements[2]) as? StringAtom + a_iterator.feed_id = body[2].parse(bert: tuple.elements[3]) as? AnyObject + a_iterator.prev = body[3].parse(bert: tuple.elements[4]) as? Int64 + a_iterator.next = body[4].parse(bert: tuple.elements[5]) as? Int64 + a_iterator.feeds = body[5].parse(bert: tuple.elements[6]) as? [AnyObject] + return a_iterator + case "log": + if body.count != 6 { return nil } + let a_log = log() + a_log.id = body[0].parse(bert: tuple.elements[1]) as? Int64 + a_log.top = body[1].parse(bert: tuple.elements[2]) as? Int64 + a_log.rear = body[2].parse(bert: tuple.elements[3]) as? Int64 + a_log.count = body[3].parse(bert: tuple.elements[4]) as? Int64 + return a_log + case "operation": + if body.count != 9 { return nil } + let a_operation = operation() + a_operation.id = body[0].parse(bert: tuple.elements[1]) as? Int64 + a_operation.container = body[1].parse(bert: tuple.elements[2]) as? StringAtom + a_operation.feed_id = body[2].parse(bert: tuple.elements[3]) as? AnyObject + a_operation.prev = body[3].parse(bert: tuple.elements[4]) as? Int64 + a_operation.next = body[4].parse(bert: tuple.elements[5]) as? Int64 + a_operation.feeds = body[5].parse(bert: tuple.elements[6]) as? [AnyObject] + return a_operation + case "feed": + if body.count != 5 { return nil } + let a_feed = feed() + a_feed.id = body[0].parse(bert: tuple.elements[1]) as? Int64 + a_feed.top = body[1].parse(bert: tuple.elements[2]) as? Int64 + a_feed.rear = body[2].parse(bert: tuple.elements[3]) as? Int64 + a_feed.count = body[3].parse(bert: tuple.elements[4]) as? Int64 + return a_feed + case "task": + if body.count != 4 { return nil } + let a_task = task() + a_task.name = body[0].parse(bert: tuple.elements[1]) as? StringAtom + a_task.module = body[1].parse(bert: tuple.elements[2]) as? StringAtom + a_task.prompt = body[2].parse(bert: tuple.elements[3]) as? [AnyObject] + a_task.roles = body[3].parse(bert: tuple.elements[4]) as? String + return a_task + case "userTask": + if body.count != 4 { return nil } + let a_userTask = userTask() + a_userTask.name = body[0].parse(bert: tuple.elements[1]) as? StringAtom + a_userTask.module = body[1].parse(bert: tuple.elements[2]) as? StringAtom + a_userTask.prompt = body[2].parse(bert: tuple.elements[3]) as? [AnyObject] + a_userTask.roles = body[3].parse(bert: tuple.elements[4]) as? String + return a_userTask + case "serviceTask": + if body.count != 4 { return nil } + let a_serviceTask = serviceTask() + a_serviceTask.name = body[0].parse(bert: tuple.elements[1]) as? StringAtom + a_serviceTask.module = body[1].parse(bert: tuple.elements[2]) as? StringAtom + a_serviceTask.prompt = body[2].parse(bert: tuple.elements[3]) as? [AnyObject] + a_serviceTask.roles = body[3].parse(bert: tuple.elements[4]) as? String + return a_serviceTask + case "receiveTask": + if body.count != 4 { return nil } + let a_receiveTask = receiveTask() + a_receiveTask.name = body[0].parse(bert: tuple.elements[1]) as? StringAtom + a_receiveTask.module = body[1].parse(bert: tuple.elements[2]) as? StringAtom + a_receiveTask.prompt = body[2].parse(bert: tuple.elements[3]) as? [AnyObject] + a_receiveTask.roles = body[3].parse(bert: tuple.elements[4]) as? String + return a_receiveTask + case "messageEvent": + if body.count != 5 { return nil } + let a_messageEvent = messageEvent() + a_messageEvent.name = body[0].parse(bert: tuple.elements[1]) as? StringAtom + a_messageEvent.module = body[1].parse(bert: tuple.elements[2]) as? StringAtom + a_messageEvent.prompt = body[2].parse(bert: tuple.elements[3]) as? [AnyObject] + a_messageEvent.payload = body[3].parse(bert: tuple.elements[4]) as? String + a_messageEvent.timeout = body[4].parse(bert: tuple.elements[5]) as? [AnyObject] + return a_messageEvent + case "boundaryEvent": + if body.count != 8 { return nil } + let a_boundaryEvent = boundaryEvent() + a_boundaryEvent.name = body[0].parse(bert: tuple.elements[1]) as? StringAtom + a_boundaryEvent.module = body[1].parse(bert: tuple.elements[2]) as? StringAtom + a_boundaryEvent.prompt = body[2].parse(bert: tuple.elements[3]) as? [AnyObject] + a_boundaryEvent.payload = body[3].parse(bert: tuple.elements[4]) as? String + a_boundaryEvent.timeout = body[4].parse(bert: tuple.elements[5]) as? [AnyObject] + a_boundaryEvent.timeDate = body[5].parse(bert: tuple.elements[6]) as? String + a_boundaryEvent.timeDuration = body[6].parse(bert: tuple.elements[7]) as? String + a_boundaryEvent.timeCycle = body[7].parse(bert: tuple.elements[8]) as? String + return a_boundaryEvent + case "timeoutEvent": + if body.count != 8 { return nil } + let a_timeoutEvent = timeoutEvent() + a_timeoutEvent.name = body[0].parse(bert: tuple.elements[1]) as? StringAtom + a_timeoutEvent.module = body[1].parse(bert: tuple.elements[2]) as? StringAtom + a_timeoutEvent.prompt = body[2].parse(bert: tuple.elements[3]) as? [AnyObject] + a_timeoutEvent.payload = body[3].parse(bert: tuple.elements[4]) as? String + a_timeoutEvent.timeout = body[4].parse(bert: tuple.elements[5]) as? [AnyObject] + a_timeoutEvent.timeDate = body[5].parse(bert: tuple.elements[6]) as? String + a_timeoutEvent.timeDuration = body[6].parse(bert: tuple.elements[7]) as? String + a_timeoutEvent.timeCycle = body[7].parse(bert: tuple.elements[8]) as? String + return a_timeoutEvent + case "beginEvent": + if body.count != 3 { return nil } + let a_beginEvent = beginEvent() + a_beginEvent.name = body[0].parse(bert: tuple.elements[1]) as? StringAtom + a_beginEvent.module = body[1].parse(bert: tuple.elements[2]) as? StringAtom + a_beginEvent.prompt = body[2].parse(bert: tuple.elements[3]) as? [AnyObject] + return a_beginEvent + case "endEvent": + if body.count != 3 { return nil } + let a_endEvent = endEvent() + a_endEvent.name = body[0].parse(bert: tuple.elements[1]) as? StringAtom + a_endEvent.module = body[1].parse(bert: tuple.elements[2]) as? StringAtom + a_endEvent.prompt = body[2].parse(bert: tuple.elements[3]) as? [AnyObject] + return a_endEvent + case "sequenceFlow": + if body.count != 2 { return nil } + let a_sequenceFlow = sequenceFlow() + a_sequenceFlow.source = body[0].parse(bert: tuple.elements[1]) as? StringAtom + a_sequenceFlow.target = body[1].parse(bert: tuple.elements[2]) as? AnyObject + return a_sequenceFlow + case "hist": + if body.count != 10 { return nil } + let a_hist = hist() + a_hist.id = body[0].parse(bert: tuple.elements[1]) as? Int64 + a_hist.container = body[1].parse(bert: tuple.elements[2]) as? StringAtom + a_hist.feed_id = body[2].parse(bert: tuple.elements[3]) as? AnyObject + a_hist.prev = body[3].parse(bert: tuple.elements[4]) as? Int64 + a_hist.next = body[4].parse(bert: tuple.elements[5]) as? Int64 + a_hist.feeds = body[5].parse(bert: tuple.elements[6]) as? [AnyObject] + a_hist.name = body[6].parse(bert: tuple.elements[7]) as? String + a_hist.task = body[7].parse(bert: tuple.elements[8]) as? StringAtom + a_hist.docs = body[8].parse(bert: tuple.elements[9]) as? [AnyObject] + a_hist.time = body[9].parse(bert: tuple.elements[10]) as? AnyObject + return a_hist + case "process": + if body.count != 22 { return nil } + let a_process = process() + a_process.id = body[0].parse(bert: tuple.elements[1]) as? Int64 + a_process.container = body[1].parse(bert: tuple.elements[2]) as? StringAtom + a_process.feed_id = body[2].parse(bert: tuple.elements[3]) as? AnyObject + a_process.prev = body[3].parse(bert: tuple.elements[4]) as? Int64 + a_process.next = body[4].parse(bert: tuple.elements[5]) as? Int64 + a_process.feeds = body[5].parse(bert: tuple.elements[6]) as? [AnyObject] + a_process.name = body[6].parse(bert: tuple.elements[7]) as? String + a_process.roles = body[7].parse(bert: tuple.elements[8]) as? [AnyObject] + a_process.tasks = body[8].parse(bert: tuple.elements[9]) as? [AnyObject] + a_process.events = body[9].parse(bert: tuple.elements[10]) as? [AnyObject] + a_process.hist = body[10].parse(bert: tuple.elements[11]) as? AnyObject + a_process.flows = body[11].parse(bert: tuple.elements[12]) as? [sequenceFlow] + a_process.rules = body[12].parse(bert: tuple.elements[13]) as? AnyObject + a_process.docs = body[13].parse(bert: tuple.elements[14]) as? [AnyObject] + a_process.options = body[14].parse(bert: tuple.elements[15]) as? AnyObject + a_process.task = body[15].parse(bert: tuple.elements[16]) as? StringAtom + a_process.timer = body[16].parse(bert: tuple.elements[17]) as? String + a_process.notifications = body[17].parse(bert: tuple.elements[18]) as? AnyObject + a_process.result = body[18].parse(bert: tuple.elements[19]) as? String + a_process.started = body[19].parse(bert: tuple.elements[20]) as? [AnyObject] + a_process.beginEvent = body[20].parse(bert: tuple.elements[21]) as? StringAtom + a_process.endEvent = body[21].parse(bert: tuple.elements[22]) as? StringAtom + return a_process + case "complete": + if body.count != 1 { return nil } + let a_complete = complete() + a_complete.id = body[0].parse(bert: tuple.elements[1]) as? Int64 + return a_complete + case "proc": + if body.count != 1 { return nil } + let a_proc = proc() + a_proc.id = body[0].parse(bert: tuple.elements[1]) as? Int64 + return a_proc + case "load": + if body.count != 1 { return nil } + let a_load = load() + a_load.id = body[0].parse(bert: tuple.elements[1]) as? Int64 + return a_load + case "histo": + if body.count != 1 { return nil } + let a_histo = histo() + a_histo.id = body[0].parse(bert: tuple.elements[1]) as? Int64 + return a_histo + case "create": + if body.count != 2 { return nil } + let a_create = create() + a_create.proc = body[0].parse(bert: tuple.elements[1]) as? AnyObject + a_create.docs = body[1].parse(bert: tuple.elements[2]) as? [AnyObject] + return a_create + case "amend": + if body.count != 2 { return nil } + let a_amend = amend() + a_amend.id = body[0].parse(bert: tuple.elements[1]) as? Int64 + a_amend.docs = body[1].parse(bert: tuple.elements[2]) as? [AnyObject] + return a_amend case "chain": if body.count != 6 { return nil } let a_chain = chain() @@ -26,7 +266,7 @@ func parseObject(name: String, body:[Model], tuple: BertTuple) -> AnyObject? a_Search.ref = body[1].parse(bert: tuple.elements[2]) as? String a_Search.field = body[2].parse(bert: tuple.elements[3]) as? String a_Search.type = body[3].parse(bert: tuple.elements[4]) as? AnyObject - a_Search.value = body[4].parse(bert: tuple.elements[5]) as? AnyObject + a_Search.value = body[4].parse(bert: tuple.elements[5]) as? [AnyObject] a_Search.status = body[5].parse(bert: tuple.elements[6]) as? AnyObject return a_Search case "p2p": @@ -130,13 +370,12 @@ func parseObject(name: String, body:[Model], tuple: BertTuple) -> AnyObject? a_Message.status = body[15].parse(bert: tuple.elements[16]) as? AnyObject return a_Message case "Link": - if body.count != 5 { return nil } + if body.count != 4 { return nil } let a_Link = Link() a_Link.id = body[0].parse(bert: tuple.elements[1]) as? String a_Link.name = body[1].parse(bert: tuple.elements[2]) as? String - a_Link.room_id = body[2].parse(bert: tuple.elements[3]) as? String - a_Link.created = body[3].parse(bert: tuple.elements[4]) as? Int64 - a_Link.status = body[4].parse(bert: tuple.elements[5]) as? AnyObject + a_Link.type = body[2].parse(bert: tuple.elements[3]) as? AnyObject + a_Link.status = body[3].parse(bert: tuple.elements[4]) as? AnyObject return a_Link case "Room": if body.count != 18 { return nil } @@ -155,7 +394,7 @@ func parseObject(name: String, body:[Model], tuple: BertTuple) -> AnyObject? a_Room.unread = body[11].parse(bert: tuple.elements[12]) as? Int64 a_Room.mentions = body[12].parse(bert: tuple.elements[13]) as? [AnyObject] a_Room.readers = body[13].parse(bert: tuple.elements[14]) as? [AnyObject] - a_Room.last_msg = body[14].parse(bert: tuple.elements[15]) as? Message + a_Room.last_msg = body[14].parse(bert: tuple.elements[15]) as? AnyObject a_Room.update = body[15].parse(bert: tuple.elements[16]) as? Int64 a_Room.created = body[16].parse(bert: tuple.elements[17]) as? Int64 a_Room.status = body[17].parse(bert: tuple.elements[18]) as? AnyObject @@ -192,7 +431,7 @@ func parseObject(name: String, body:[Model], tuple: BertTuple) -> AnyObject? a_Contact.names = body[2].parse(bert: tuple.elements[3]) as? String a_Contact.surnames = body[3].parse(bert: tuple.elements[4]) as? String a_Contact.nick = body[4].parse(bert: tuple.elements[5]) as? String - a_Contact.reader = body[5].parse(bert: tuple.elements[6]) as? [AnyObject] + a_Contact.reader = body[5].parse(bert: tuple.elements[6]) as? AnyObject a_Contact.unread = body[6].parse(bert: tuple.elements[7]) as? Int64 a_Contact.last_msg = body[7].parse(bert: tuple.elements[8]) as? Message a_Contact.update = body[8].parse(bert: tuple.elements[9]) as? Int64 @@ -214,12 +453,12 @@ func parseObject(name: String, body:[Model], tuple: BertTuple) -> AnyObject? a_Auth.client_id = body[0].parse(bert: tuple.elements[1]) as? String a_Auth.dev_key = body[1].parse(bert: tuple.elements[2]) as? String a_Auth.user_id = body[2].parse(bert: tuple.elements[3]) as? String - a_Auth.phone = body[3].parse(bert: tuple.elements[4]) as? String + a_Auth.phone = body[3].parse(bert: tuple.elements[4]) as? AnyObject a_Auth.token = body[4].parse(bert: tuple.elements[5]) as? String a_Auth.type = body[5].parse(bert: tuple.elements[6]) as? StringAtom a_Auth.sms_code = body[6].parse(bert: tuple.elements[7]) as? String a_Auth.attempts = body[7].parse(bert: tuple.elements[8]) as? Int64 - a_Auth.services = body[8].parse(bert: tuple.elements[9]) as? [String] + a_Auth.services = body[8].parse(bert: tuple.elements[9]) as? [AnyObject] a_Auth.settings = body[9].parse(bert: tuple.elements[10]) as? [Feature] a_Auth.push = body[10].parse(bert: tuple.elements[11]) as? String a_Auth.os = body[11].parse(bert: tuple.elements[12]) as? AnyObject @@ -229,14 +468,14 @@ func parseObject(name: String, body:[Model], tuple: BertTuple) -> AnyObject? case "Roster": if body.count != 13 { return nil } let a_Roster = Roster() - a_Roster.id = body[0].parse(bert: tuple.elements[1]) as? Int64 + a_Roster.id = body[0].parse(bert: tuple.elements[1]) as? AnyObject a_Roster.names = body[1].parse(bert: tuple.elements[2]) as? String a_Roster.surnames = body[2].parse(bert: tuple.elements[3]) as? String a_Roster.email = body[3].parse(bert: tuple.elements[4]) as? String a_Roster.nick = body[4].parse(bert: tuple.elements[5]) as? String - a_Roster.userlist = body[5].parse(bert: tuple.elements[6]) as? [Contact] + a_Roster.userlist = body[5].parse(bert: tuple.elements[6]) as? [AnyObject] a_Roster.roomlist = body[6].parse(bert: tuple.elements[7]) as? [Room] - a_Roster.favorite = body[7].parse(bert: tuple.elements[8]) as? [ExtendedStar] + a_Roster.favorite = body[7].parse(bert: tuple.elements[8]) as? [Star] a_Roster.tags = body[8].parse(bert: tuple.elements[9]) as? [Tag] a_Roster.phone = body[9].parse(bert: tuple.elements[10]) as? String a_Roster.avatar = body[10].parse(bert: tuple.elements[11]) as? String @@ -255,6 +494,12 @@ func parseObject(name: String, body:[Model], tuple: BertTuple) -> AnyObject? a_Profile.presence = body[6].parse(bert: tuple.elements[7]) as? AnyObject a_Profile.status = body[7].parse(bert: tuple.elements[8]) as? AnyObject return a_Profile + case "Presence": + if body.count != 2 { return nil } + let a_Presence = Presence() + a_Presence.uid = body[0].parse(bert: tuple.elements[1]) as? String + a_Presence.status = body[1].parse(bert: tuple.elements[2]) as? AnyObject + return a_Presence case "Friend": if body.count != 4 { return nil } let a_Friend = Friend() @@ -273,6 +518,7 @@ func parseObject(name: String, body:[Model], tuple: BertTuple) -> AnyObject? if body.count != 12 { return nil } let a_Job = Job() a_Job.id = body[0].parse(bert: tuple.elements[1]) as? Int64 + a_Job.container = body[1].parse(bert: tuple.elements[2]) as? AnyObject a_Job.feed_id = body[2].parse(bert: tuple.elements[3]) as? act a_Job.prev = body[3].parse(bert: tuple.elements[4]) as? Int64 a_Job.next = body[4].parse(bert: tuple.elements[5]) as? Int64 @@ -308,6 +554,12 @@ func parseObject(name: String, body:[Model], tuple: BertTuple) -> AnyObject? a_Index.id = body[0].parse(bert: tuple.elements[1]) as? AnyObject a_Index.roster = body[1].parse(bert: tuple.elements[2]) as? [AnyObject] return a_Index + case "Whitelist": + if body.count != 2 { return nil } + let a_Whitelist = Whitelist() + a_Whitelist.phone = body[0].parse(bert: tuple.elements[1]) as? String + a_Whitelist.created = body[1].parse(bert: tuple.elements[2]) as? Int64 + return a_Whitelist case "error": if body.count != 1 { return nil } let a_error = error() @@ -336,6 +588,12 @@ func parseObject(name: String, body:[Model], tuple: BertTuple) -> AnyObject? a_io.code = body[0].parse(bert: tuple.elements[1]) as? AnyObject a_io.data = body[1].parse(bert: tuple.elements[2]) as? AnyObject return a_io + case "Ack": + if body.count != 2 { return nil } + let a_Ack = Ack() + a_Ack.table = body[0].parse(bert: tuple.elements[1]) as? StringAtom + a_Ack.id = body[1].parse(bert: tuple.elements[2]) as? String + return a_Ack case "errors": if body.count != 2 { return nil } let a_errors = errors() @@ -359,6 +617,30 @@ func parseObject(name: String, body:[Model], tuple: BertTuple) -> AnyObject? a_PublishService.topic = body[1].parse(bert: tuple.elements[2]) as? String a_PublishService.qos = body[2].parse(bert: tuple.elements[3]) as? Int64 return a_PublishService + case "FakeNumbers": + if body.count != 2 { return nil } + let a_FakeNumbers = FakeNumbers() + a_FakeNumbers.phone = body[0].parse(bert: tuple.elements[1]) as? String + a_FakeNumbers.created = body[1].parse(bert: tuple.elements[2]) as? Int64 + return a_FakeNumbers + case "cx": + if body.count != 14 { return nil } + let a_cx = cx() + a_cx.handlers = body[0].parse(bert: tuple.elements[1]) as? [handler] + a_cx.actions = body[1].parse(bert: tuple.elements[2]) as? [AnyObject] + a_cx.req = body[2].parse(bert: tuple.elements[3]) as? AnyObject + a_cx.module = body[3].parse(bert: tuple.elements[4]) as? StringAtom + a_cx.lang = body[4].parse(bert: tuple.elements[5]) as? StringAtom + a_cx.path = body[5].parse(bert: tuple.elements[6]) as? String + a_cx.session = body[6].parse(bert: tuple.elements[7]) as? String + a_cx.formatter = body[7].parse(bert: tuple.elements[8]) as? AnyObject + a_cx.params = body[8].parse(bert: tuple.elements[9]) as? [AnyObject] + a_cx.node = body[9].parse(bert: tuple.elements[10]) as? StringAtom + a_cx.client_pid = body[10].parse(bert: tuple.elements[11]) as? AnyObject + a_cx.state = body[11].parse(bert: tuple.elements[12]) as? AnyObject + a_cx.from = body[12].parse(bert: tuple.elements[13]) as? String + a_cx.vsn = body[13].parse(bert: tuple.elements[14]) as? String + return a_cx default: return nil } } \ No newline at end of file diff --git a/apps/roster/priv/macbert/Spec/Ack_Spec.swift b/apps/roster/priv/macbert/Spec/Ack_Spec.swift new file mode 100644 index 0000000000000000000000000000000000000000..d27123e9a08dbff18d462399e669f412073022e6 --- /dev/null +++ b/apps/roster/priv/macbert/Spec/Ack_Spec.swift @@ -0,0 +1,6 @@ +func get_Ack() -> Model { + return Model(value:Tuple(name:"Ack",body:[ + Model(value:Atom()), + Model(value:Chain(types:[ + Model(value:List(constant:"")), + Model(value:Binary())]))]))} diff --git a/apps/roster/priv/macbert/Spec/Auth_Spec.swift b/apps/roster/priv/macbert/Spec/Auth_Spec.swift index dc05a6492e564f8225e62e9b0ad0bab906b51b85..44b108bb905da016c4efa160cdbf1aba13857490 100644 --- a/apps/roster/priv/macbert/Spec/Auth_Spec.swift +++ b/apps/roster/priv/macbert/Spec/Auth_Spec.swift @@ -11,7 +11,10 @@ func get_Auth() -> Model { Model(value:Binary())])), Model(value:Chain(types:[ Model(value:List(constant:"")), - Model(value:Binary())])), + Model(value:Binary()), + Model(value:Tuple(name:nil,body:[ + Model(value:Atom(constant:"fake")), + Model(value:Binary())]))])), Model(value:Chain(types:[ Model(value:List(constant:"")), Model(value:Binary())])), @@ -24,8 +27,10 @@ func get_Auth() -> Model { Model(value:Chain(types:[ Model(value:List(constant:"")), Model(value:Number())])), - Model(value:List(constant:nil, model:Model(value:Atom()))), - Model(value:List(constant:nil,model:get_Feature())), + Model(value:List(constant:nil, model:Model(value:Chain(types:[Model(value:Tuple()),Model(value:Atom()),Model(value:Binary()),Model(value:Number()),Model(value:List(constant:""))])))), + Model(value:Chain(types:[ + Model(value:List(constant:"")), + Model(value:List(constant:nil,model:get_Feature()))])), Model(value:Chain(types:[ Model(value:List(constant:"")), Model(value:Binary())])), diff --git a/apps/roster/priv/macbert/Spec/Contact_Spec.swift b/apps/roster/priv/macbert/Spec/Contact_Spec.swift index 23662eb15828f73a517e69591b2ad7f7e3c39710..5271e6a07758b82e81dba77a9fa3a139f23c2b25 100644 --- a/apps/roster/priv/macbert/Spec/Contact_Spec.swift +++ b/apps/roster/priv/macbert/Spec/Contact_Spec.swift @@ -17,6 +17,7 @@ func get_Contact() -> Model { Model(value:Binary())])), Model(value:Chain(types:[ Model(value:List(constant:"")), + Model(value:Number()), Model(value:List(constant:nil, model:Model(value:Number())))])), Model(value:Chain(types:[ Model(value:List(constant:"")), @@ -35,7 +36,8 @@ func get_Contact() -> Model { Model(value:Chain(types:[ Model(value:List(constant:"")), Model(value:Atom(constant:"online")), - Model(value:Atom(constant:"offline"))])), + Model(value:Atom(constant:"offline")), + Model(value:Binary())])), Model(value:Chain(types:[ Model(value:List(constant:"")), Model(value:Atom(constant:"request")), diff --git a/apps/roster/priv/macbert/Spec/ExtendedStar_Spec.swift b/apps/roster/priv/macbert/Spec/ExtendedStar_Spec.swift index 719d23fb142598f5b65a90c556b2eaec43da7485..45d891a2a3c4667dd53a80c965d92cee014bc5fd 100644 --- a/apps/roster/priv/macbert/Spec/ExtendedStar_Spec.swift +++ b/apps/roster/priv/macbert/Spec/ExtendedStar_Spec.swift @@ -1,6 +1,9 @@ func get_ExtendedStar() -> Model { return Model(value:Tuple(name:"ExtendedStar",body:[ - get_Star(), + Model(value:Chain(types:[ + get_Star(), + Model(value:List(constant:""))])), Model(value:Chain(types:[ get_Contact(), - get_Room()]))]))} + get_Room(), + Model(value:List(constant:""))]))]))} diff --git a/apps/roster/priv/macbert/Spec/Feature_Spec.swift b/apps/roster/priv/macbert/Spec/Feature_Spec.swift index 4dc52cc9d21b99b93f42e80d86cb5b32f0bab090..eaf569a31d7ed3653735a15a511b3e1138bea43e 100644 --- a/apps/roster/priv/macbert/Spec/Feature_Spec.swift +++ b/apps/roster/priv/macbert/Spec/Feature_Spec.swift @@ -3,12 +3,6 @@ func get_Feature() -> Model { Model(value:Chain(types:[ Model(value:List(constant:"")), Model(value:Binary())])), - Model(value:Chain(types:[ - Model(value:List(constant:"")), - Model(value:Binary())])), - Model(value:Chain(types:[ - Model(value:List(constant:"")), - Model(value:Binary())])), - Model(value:Chain(types:[ - Model(value:List(constant:"")), - Model(value:Binary())]))]))} + Model(value:Binary()), + Model(value:Binary()), + Model(value:Binary())]))} diff --git a/apps/roster/priv/macbert/Spec/Friend_Spec.swift b/apps/roster/priv/macbert/Spec/Friend_Spec.swift index 474fa29a165a2317c010c36890634eda78f19fcd..34ced364e40bbffe7d68e37f173de4c7f6e5ef6b 100644 --- a/apps/roster/priv/macbert/Spec/Friend_Spec.swift +++ b/apps/roster/priv/macbert/Spec/Friend_Spec.swift @@ -1,14 +1,11 @@ func get_Friend() -> Model { return Model(value:Tuple(name:"Friend",body:[ + Model(value:Binary()), + Model(value:Binary()), Model(value:Chain(types:[ Model(value:List(constant:"")), - Model(value:Binary())])), + Model(value:List(constant:nil,model:get_Feature()))])), Model(value:Chain(types:[ - Model(value:List(constant:"")), - Model(value:Binary())])), - Model(value:List(constant:nil,model:get_Feature())), - Model(value:Chain(types:[ - Model(value:List(constant:"")), Model(value:Atom(constant:"ban")), Model(value:Atom(constant:"unban")), Model(value:Atom(constant:"request")), diff --git a/apps/roster/priv/macbert/Spec/History_Spec.swift b/apps/roster/priv/macbert/Spec/History_Spec.swift index b0df2acbde034cecbd994512bf9093dc8c0a13cf..879fccf916b18e3b23b905c1d9423a7a43baa47d 100644 --- a/apps/roster/priv/macbert/Spec/History_Spec.swift +++ b/apps/roster/priv/macbert/Spec/History_Spec.swift @@ -1,29 +1,38 @@ func get_History() -> Model { return Model(value:Tuple(name:"History",body:[ + Model(value:Binary()), Model(value:Chain(types:[ - Model(value:List(constant:"")), - Model(value:Binary())])), - Model(value:Chain(types:[ - Model(value:List(constant:"")), get_p2p(), get_muc(), get_act(), - get_StickerPack()])), + get_StickerPack(), + Model(value:List(constant:""))])), Model(value:Chain(types:[ Model(value:List(constant:"")), Model(value:Number())])), Model(value:Chain(types:[ Model(value:List(constant:"")), Model(value:Number())])), - Model(value:List(constant:nil, model:Model(value:Chain(types:[ - get_Message(), - get_Job(), - get_StickerPack()])))), Model(value:Chain(types:[ Model(value:List(constant:"")), + Model(value:List(constant:nil, model:Model(value:Chain(types:[ + get_Message(), + get_Job(), + get_StickerPack()]))))])), + Model(value:Chain(types:[ Model(value:Atom(constant:"updated")), Model(value:Atom(constant:"get")), Model(value:Atom(constant:"update")), Model(value:Atom(constant:"last_loaded")), Model(value:Atom(constant:"last_msg")), - Model(value:Atom(constant:"get_reply"))]))]))} + Model(value:Atom(constant:"get_reply")), + Model(value:Atom(constant:"double_get")), + Model(value:Atom(constant:"delete")), + Model(value:Atom(constant:"image")), + Model(value:Atom(constant:"video")), + Model(value:Atom(constant:"file")), + Model(value:Atom(constant:"link")), + Model(value:Atom(constant:"audio")), + Model(value:Atom(constant:"contact")), + Model(value:Atom(constant:"location")), + Model(value:Atom(constant:"text"))]))]))} diff --git a/apps/roster/priv/macbert/Spec/Index_Spec.swift b/apps/roster/priv/macbert/Spec/Index_Spec.swift index 6c1dc6b147cb2fad101fb92677ae564cb56a503d..9f7e4a1f6642ac38be619d89f6ad9ed9992ca399 100644 --- a/apps/roster/priv/macbert/Spec/Index_Spec.swift +++ b/apps/roster/priv/macbert/Spec/Index_Spec.swift @@ -3,4 +3,4 @@ func get_Index() -> Model { Model(value:Chain(types:[ Model(value:List(constant:"")), Model(value:Chain(types:[Model(value:Tuple()),Model(value:Atom()),Model(value:Binary()),Model(value:Number()),Model(value:List(constant:""))]))])), - Model(value:List(constant:nil, model:Model(value:Number())))]))} + Model(value:List(constant:nil, model:Model(value:Chain(types:[Model(value:Tuple()),Model(value:Atom()),Model(value:Binary()),Model(value:Number()),Model(value:List(constant:""))]))))]))} diff --git a/apps/roster/priv/macbert/Spec/Job_Spec.swift b/apps/roster/priv/macbert/Spec/Job_Spec.swift index fb78bb3a343edc94f11e96cb0cba1fe172a7f42f..0247cfa0cfdf76f38192135c074c0dc94a0b7d54 100644 --- a/apps/roster/priv/macbert/Spec/Job_Spec.swift +++ b/apps/roster/priv/macbert/Spec/Job_Spec.swift @@ -3,6 +3,9 @@ func get_Job() -> Model { Model(value:Chain(types:[ Model(value:List(constant:"")), Model(value:Number())])), + Model(value:Chain(types:[ + Model(value:Atom(constant:"chain")), + Model(value:List(constant:""))])), get_act(), Model(value:Chain(types:[ Model(value:List(constant:"")), @@ -18,10 +21,16 @@ func get_Job() -> Model { Model(value:List(constant:"")), Model(value:Number()), get_process()])), - Model(value:Number()), + Model(value:Chain(types:[ + Model(value:List(constant:"")), + Model(value:Number())])), Model(value:List(constant:nil,model:get_Message())), - Model(value:List(constant:nil,model:get_messageEvent())), - Model(value:List(constant:nil,model:get_Feature())), + Model(value:Chain(types:[ + Model(value:List(constant:"")), + Model(value:List(constant:nil,model:get_messageEvent()))])), + Model(value:Chain(types:[ + Model(value:List(constant:"")), + Model(value:List(constant:nil,model:get_Feature()))])), Model(value:Chain(types:[ Model(value:List(constant:"")), Model(value:Atom(constant:"init")), diff --git a/apps/roster/priv/macbert/Spec/Link_Spec.swift b/apps/roster/priv/macbert/Spec/Link_Spec.swift index 923341da93bbe2cfd4ecb217a5ebb056a4939844..9e2010f048291ee9865fb84496c6a851030eca99 100644 --- a/apps/roster/priv/macbert/Spec/Link_Spec.swift +++ b/apps/roster/priv/macbert/Spec/Link_Spec.swift @@ -8,14 +8,11 @@ func get_Link() -> Model { Model(value:Binary())])), Model(value:Chain(types:[ Model(value:List(constant:"")), - Model(value:Binary())])), - Model(value:Chain(types:[ - Model(value:List(constant:"")), - Model(value:Number())])), + Model(value:Atom(constant:"group")), + Model(value:Atom(constant:"channel"))])), Model(value:Chain(types:[ Model(value:List(constant:"")), - Model(value:Atom(constant:"gen")), - Model(value:Atom(constant:"check")), - Model(value:Atom(constant:"add")), - Model(value:Atom(constant:"delete")), - Model(value:Atom(constant:"update"))]))]))} + Model(value:Atom(constant:"get")), + Model(value:Atom(constant:"join")), + Model(value:Atom(constant:"update")), + Model(value:Atom(constant:"delete"))]))]))} diff --git a/apps/roster/priv/macbert/Spec/Member_Spec.swift b/apps/roster/priv/macbert/Spec/Member_Spec.swift index 1ebf5a0b9b099adcd311a325ca2ca1925455e59c..cf7a14112b72773544df3cee76816aff9444f03b 100644 --- a/apps/roster/priv/macbert/Spec/Member_Spec.swift +++ b/apps/roster/priv/macbert/Spec/Member_Spec.swift @@ -5,10 +5,12 @@ func get_Member() -> Model { Model(value:Number())])), Model(value:Chain(types:[ Model(value:Atom(constant:"chain")), - Model(value:Atom(constant:"cur"))])), + Model(value:Atom(constant:"cur")), + Model(value:List(constant:""))])), Model(value:Chain(types:[ get_muc(), - get_p2p()])), + get_p2p(), + Model(value:List(constant:""))])), Model(value:Chain(types:[ Model(value:List(constant:"")), Model(value:Number())])), @@ -37,8 +39,12 @@ func get_Member() -> Model { Model(value:Chain(types:[ Model(value:List(constant:"")), Model(value:Number())])), - Model(value:List(constant:nil,model:get_Feature())), - Model(value:List(constant:nil,model:get_Service())), + Model(value:Chain(types:[ + Model(value:List(constant:"")), + Model(value:List(constant:nil,model:get_Feature()))])), + Model(value:Chain(types:[ + Model(value:List(constant:"")), + Model(value:List(constant:nil,model:get_Service()))])), Model(value:Chain(types:[ Model(value:List(constant:"")), Model(value:Atom(constant:"online")), diff --git a/apps/roster/priv/macbert/Spec/Message_Spec.swift b/apps/roster/priv/macbert/Spec/Message_Spec.swift index 010cb1dc797ee4c7b115b38225105623ff6755ef..7cde1159f3dd5316ef64d98ee75bd9259fa1947d 100644 --- a/apps/roster/priv/macbert/Spec/Message_Spec.swift +++ b/apps/roster/priv/macbert/Spec/Message_Spec.swift @@ -5,7 +5,8 @@ func get_Message() -> Model { Model(value:Number())])), Model(value:Chain(types:[ Model(value:Atom(constant:"chain")), - Model(value:Atom(constant:"cur"))])), + Model(value:Atom(constant:"cur")), + Model(value:List(constant:""))])), Model(value:Chain(types:[ get_muc(), get_p2p()])), @@ -28,24 +29,28 @@ func get_Message() -> Model { Model(value:List(constant:"")), Model(value:Number())])), Model(value:List(constant:nil,model:get_Desc())), + Model(value:List(constant:nil, model:Model(value:Chain(types:[ + Model(value:Atom(constant:"sys")), + Model(value:Atom(constant:"reply")), + Model(value:Atom(constant:"forward")), + Model(value:Atom(constant:"read")), + Model(value:Atom(constant:"edited")), + Model(value:Atom(constant:"cursor"))])))), + Model(value:Chain(types:[ + Model(value:List(constant:"")), + Model(value:Number()), + get_Message()])), Model(value:Chain(types:[ Model(value:List(constant:"")), Model(value:List(constant:nil, model:Model(value:Chain(types:[ - Model(value:Atom(constant:"sys")), - Model(value:Atom(constant:"reply")), - Model(value:Atom(constant:"forward")), - Model(value:Atom(constant:"read")), - Model(value:Atom(constant:"edited")), - Model(value:Atom(constant:"cursor"))]))))])), + Model(value:Binary()), + Model(value:Number())]))))])), Model(value:Chain(types:[ Model(value:List(constant:"")), - Model(value:Number()), - get_Message()])), - Model(value:List(constant:nil, model:Model(value:Chain(types:[ - Model(value:Binary()), - Model(value:Number())])))), - Model(value:List(constant:nil, model:Model(value:Number()))), - Model(value:List(constant:nil, model:Model(value:Number()))), + Model(value:List(constant:nil, model:Model(value:Number())))])), + Model(value:Chain(types:[ + Model(value:List(constant:"")), + Model(value:List(constant:nil, model:Model(value:Number())))])), Model(value:Chain(types:[ Model(value:List(constant:"")), Model(value:Atom(constant:"async")), diff --git a/apps/roster/priv/macbert/Spec/Profile_Spec.swift b/apps/roster/priv/macbert/Spec/Profile_Spec.swift index 2e2363c6bd79d0b8b8bbcabc01cc271206464c6b..eb62e85b64a0ff0e7705ca5a41ab858198a91122 100644 --- a/apps/roster/priv/macbert/Spec/Profile_Spec.swift +++ b/apps/roster/priv/macbert/Spec/Profile_Spec.swift @@ -3,17 +3,26 @@ func get_Profile() -> Model { Model(value:Chain(types:[ Model(value:List(constant:"")), Model(value:Binary())])), - Model(value:List(constant:nil,model:get_Service())), - Model(value:List(constant:nil,model:get_Roster())), - Model(value:List(constant:nil,model:get_Feature())), + Model(value:Chain(types:[ + Model(value:List(constant:"")), + Model(value:List(constant:nil,model:get_Service()))])), + Model(value:Chain(types:[ + Model(value:List(constant:"")), + Model(value:List(constant:nil,model:get_Roster()))])), + Model(value:Chain(types:[ + Model(value:List(constant:"")), + Model(value:List(constant:nil,model:get_Feature()))])), Model(value:Number()), Model(value:Number()), Model(value:Chain(types:[ Model(value:List(constant:"")), Model(value:Atom(constant:"offline")), - Model(value:Atom(constant:"online"))])), + Model(value:Atom(constant:"online")), + Model(value:Binary())])), Model(value:Chain(types:[ Model(value:List(constant:"")), Model(value:Atom(constant:"remove")), Model(value:Atom(constant:"get")), - Model(value:Atom(constant:"patch"))]))]))} + Model(value:Atom(constant:"patch")), + Model(value:Atom(constant:"update")), + Model(value:Atom(constant:"delete"))]))]))} diff --git a/apps/roster/priv/macbert/Spec/Room_Spec.swift b/apps/roster/priv/macbert/Spec/Room_Spec.swift index 51ae40ebee4d15e5709cfde686891d2d0c8d2bc8..2bc7347cecb1a4d6c50186e41d5d89a0337489cd 100644 --- a/apps/roster/priv/macbert/Spec/Room_Spec.swift +++ b/apps/roster/priv/macbert/Spec/Room_Spec.swift @@ -33,6 +33,7 @@ func get_Room() -> Model { Model(value:List(constant:nil, model:Model(value:Number()))), Model(value:Chain(types:[ Model(value:List(constant:"")), + Model(value:Number()), get_Message()])), Model(value:Chain(types:[ Model(value:List(constant:"")), @@ -46,7 +47,12 @@ func get_Room() -> Model { Model(value:Atom(constant:"leave")), Model(value:Atom(constant:"add")), Model(value:Atom(constant:"remove")), + Model(value:Atom(constant:"removed")), + Model(value:Atom(constant:"join")), + Model(value:Atom(constant:"info")), Model(value:Atom(constant:"patch")), Model(value:Atom(constant:"get")), Model(value:Atom(constant:"delete")), - Model(value:Atom(constant:"last_msg"))]))]))} + Model(value:Atom(constant:"last_msg")), + Model(value:Atom(constant:"mute")), + Model(value:Atom(constant:"unmute"))]))]))} diff --git a/apps/roster/priv/macbert/Spec/Roster_Spec.swift b/apps/roster/priv/macbert/Spec/Roster_Spec.swift index c042fb3d77d162c684b597e19f052e6cdfba310e..9fc58594c9f0cb3dbe0cd5a931395ad49b63a9dc 100644 --- a/apps/roster/priv/macbert/Spec/Roster_Spec.swift +++ b/apps/roster/priv/macbert/Spec/Roster_Spec.swift @@ -2,6 +2,7 @@ func get_Roster() -> Model { return Model(value:Tuple(name:"Roster",body:[ Model(value:Chain(types:[ Model(value:List(constant:"")), + Model(value:Binary()), Model(value:Number())])), Model(value:Chain(types:[ Model(value:List(constant:"")), @@ -15,9 +16,11 @@ func get_Roster() -> Model { Model(value:Chain(types:[ Model(value:List(constant:"")), Model(value:Binary())])), - Model(value:List(constant:nil,model:get_Contact())), + Model(value:List(constant:nil, model:Model(value:Chain(types:[ + get_Contact(), + Model(value:Number())])))), Model(value:List(constant:nil,model:get_Room())), - Model(value:List(constant:nil,model:get_ExtendedStar())), + Model(value:List(constant:nil,model:get_Star())), Model(value:List(constant:nil,model:get_Tag())), Model(value:Chain(types:[ Model(value:List(constant:"")), diff --git a/apps/roster/priv/macbert/Spec/Search_Spec.swift b/apps/roster/priv/macbert/Spec/Search_Spec.swift index 414234788f690b57f8b706607f21c22d0378b4e6..c5be4d12b5fbcc9afdc5c49f6b95477dacdb310c 100644 --- a/apps/roster/priv/macbert/Spec/Search_Spec.swift +++ b/apps/roster/priv/macbert/Spec/Search_Spec.swift @@ -1,24 +1,14 @@ func get_Search() -> Model { return Model(value:Tuple(name:"Search",body:[ + Model(value:Number()), + Model(value:Binary()), + Model(value:Binary()), Model(value:Chain(types:[ - Model(value:List(constant:"")), - Model(value:Number())])), - Model(value:Chain(types:[ - Model(value:List(constant:"")), - Model(value:Binary())])), - Model(value:Chain(types:[ - Model(value:List(constant:"")), - Model(value:Binary())])), - Model(value:Chain(types:[ - Model(value:List(constant:"")), Model(value:Atom(constant:"==")), Model(value:Atom(constant:"!=")), Model(value:Atom(constant:"like"))])), + Model(value:List(constant:nil, model:Model(value:Chain(types:[Model(value:Tuple()),Model(value:Atom()),Model(value:Binary()),Model(value:Number()),Model(value:List(constant:""))])))), Model(value:Chain(types:[ - Model(value:List(constant:"")), - Model(value:Chain(types:[Model(value:Tuple()),Model(value:Atom()),Model(value:Binary()),Model(value:Number()),Model(value:List(constant:""))]))])), - Model(value:Chain(types:[ - Model(value:List(constant:"")), Model(value:Atom(constant:"profile")), Model(value:Atom(constant:"roster")), Model(value:Atom(constant:"contact")), diff --git a/apps/roster/priv/macbert/Spec/Service_Spec.swift b/apps/roster/priv/macbert/Spec/Service_Spec.swift index 6db365f1a93c8325388d2f6353c0b4da0fd06f65..3360f99552aa92346b686dc8a6f0fa4204574f4c 100644 --- a/apps/roster/priv/macbert/Spec/Service_Spec.swift +++ b/apps/roster/priv/macbert/Spec/Service_Spec.swift @@ -4,7 +4,6 @@ func get_Service() -> Model { Model(value:List(constant:"")), Model(value:Binary())])), Model(value:Chain(types:[ - Model(value:List(constant:"")), Model(value:Atom(constant:"email")), Model(value:Atom(constant:"vox")), Model(value:Atom(constant:"aws")), @@ -22,4 +21,6 @@ func get_Service() -> Model { Model(value:Chain(types:[ Model(value:List(constant:"")), Model(value:Atom(constant:"verified")), - Model(value:Atom(constant:"added"))]))]))} + Model(value:Atom(constant:"added")), + Model(value:Atom(constant:"add")), + Model(value:Atom(constant:"remove"))]))]))} diff --git a/apps/roster/priv/macbert/Spec/amend_Spec.swift b/apps/roster/priv/macbert/Spec/amend_Spec.swift index 873e2f9080b6f3522bde645dcd1139d4c6363530..7a08766c3fb5e37d5fe4894ee2be30eeb609f8fc 100644 --- a/apps/roster/priv/macbert/Spec/amend_Spec.swift +++ b/apps/roster/priv/macbert/Spec/amend_Spec.swift @@ -5,7 +5,4 @@ func get_amend() -> Model { Model(value:Number())])), Model(value:Chain(types:[ Model(value:List(constant:"")), - Model(value:List(constant:nil, model:Model(value:Chain(types:[ - get_join_application(), - get_max_tour(), - get_tour_list()]))))]))]))} + Model(value:List(constant:nil, model:Model(value:Tuple())))]))]))} diff --git a/apps/roster/priv/macbert/Spec/beginEvent_Spec.swift b/apps/roster/priv/macbert/Spec/beginEvent_Spec.swift index 713b3af1cec2131a948e46e30c0ce23f3ccb1330..f7eefc2ee9d998d7c5a1d2e3fd8033851016e374 100644 --- a/apps/roster/priv/macbert/Spec/beginEvent_Spec.swift +++ b/apps/roster/priv/macbert/Spec/beginEvent_Spec.swift @@ -5,4 +5,5 @@ func get_beginEvent() -> Model { Model(value:Atom())])), Model(value:Chain(types:[ Model(value:List(constant:"")), - Model(value:Atom())]))]))} + Model(value:Atom())])), + Model(value:List(constant:nil, model:Model(value:Tuple())))]))} diff --git a/apps/roster/priv/macbert/Spec/boundaryEvent_Spec.swift b/apps/roster/priv/macbert/Spec/boundaryEvent_Spec.swift index d35648ba9cd10860c391ec6aba095b66dc8dee48..e40ced10e38016d46d8ac5d940487592fd2859c6 100644 --- a/apps/roster/priv/macbert/Spec/boundaryEvent_Spec.swift +++ b/apps/roster/priv/macbert/Spec/boundaryEvent_Spec.swift @@ -3,6 +3,10 @@ func get_boundaryEvent() -> Model { Model(value:Chain(types:[ Model(value:List(constant:"")), Model(value:Atom())])), + Model(value:Chain(types:[ + Model(value:List(constant:"")), + Model(value:Atom())])), + Model(value:List(constant:nil, model:Model(value:Tuple()))), Model(value:Binary()), Model(value:Tuple(name:nil,body:[ Model(value:Number()), @@ -12,7 +16,4 @@ func get_boundaryEvent() -> Model { Model(value:Number())]))])), Model(value:Binary()), Model(value:Binary()), - Model(value:Binary()), - Model(value:Chain(types:[ - Model(value:List(constant:"")), - Model(value:Atom())]))]))} + Model(value:Binary())]))} diff --git a/apps/roster/priv/macbert/Spec/create_Spec.swift b/apps/roster/priv/macbert/Spec/create_Spec.swift index 32a6a65b67cec95b9c1e4af99631327aa8fe30cb..40aeb3b4656736b0447a0b129851c011aaf6cc49 100644 --- a/apps/roster/priv/macbert/Spec/create_Spec.swift +++ b/apps/roster/priv/macbert/Spec/create_Spec.swift @@ -6,7 +6,4 @@ func get_create() -> Model { Model(value:Binary())])), Model(value:Chain(types:[ Model(value:List(constant:"")), - Model(value:List(constant:nil, model:Model(value:Chain(types:[ - get_join_application(), - get_max_tour(), - get_tour_list()]))))]))]))} + Model(value:List(constant:nil, model:Model(value:Tuple())))]))]))} diff --git a/apps/roster/priv/macbert/Spec/endEvent_Spec.swift b/apps/roster/priv/macbert/Spec/endEvent_Spec.swift index 45b898c9cc82351bef294dafc195194554552fd8..9cd2fe816963c24173d42ca0cccb9ac7ed4cdf24 100644 --- a/apps/roster/priv/macbert/Spec/endEvent_Spec.swift +++ b/apps/roster/priv/macbert/Spec/endEvent_Spec.swift @@ -5,4 +5,5 @@ func get_endEvent() -> Model { Model(value:Atom())])), Model(value:Chain(types:[ Model(value:List(constant:"")), - Model(value:Atom())]))]))} + Model(value:Atom())])), + Model(value:List(constant:nil, model:Model(value:Tuple())))]))} diff --git a/apps/roster/priv/macbert/Spec/hist_Spec.swift b/apps/roster/priv/macbert/Spec/hist_Spec.swift index 1865ce2ddbf71291f4974cdfa0acafc1413204aa..5859163c680205f71eb5b5538a6a2701b51eae07 100644 --- a/apps/roster/priv/macbert/Spec/hist_Spec.swift +++ b/apps/roster/priv/macbert/Spec/hist_Spec.swift @@ -16,4 +16,5 @@ func get_hist() -> Model { Model(value:List(constant:"")), Model(value:Binary())])), Model(value:Atom()), + Model(value:List(constant:nil, model:Model(value:Tuple()))), Model(value:Chain(types:[Model(value:Tuple()),Model(value:Atom()),Model(value:Binary()),Model(value:Number()),Model(value:List(constant:""))]))]))} diff --git a/apps/roster/priv/macbert/Spec/messageEvent_Spec.swift b/apps/roster/priv/macbert/Spec/messageEvent_Spec.swift index e2102f8a492143afe45c8df71e1d4cd1312f187d..c9af1dbf776247b1e04733e9d2aeb0934fda271d 100644 --- a/apps/roster/priv/macbert/Spec/messageEvent_Spec.swift +++ b/apps/roster/priv/macbert/Spec/messageEvent_Spec.swift @@ -3,11 +3,14 @@ func get_messageEvent() -> Model { Model(value:Chain(types:[ Model(value:List(constant:"")), Model(value:Atom())])), + Model(value:Chain(types:[ + Model(value:List(constant:"")), + Model(value:Atom())])), + Model(value:List(constant:nil, model:Model(value:Tuple()))), Model(value:Binary()), Model(value:Tuple(name:nil,body:[ Model(value:Number()), Model(value:Tuple(name:nil,body:[ Model(value:Number()), Model(value:Number()), - Model(value:Number())]))])), - Model(value:Atom())]))} + Model(value:Number())]))]))]))} diff --git a/apps/roster/priv/macbert/Spec/muc_Spec.swift b/apps/roster/priv/macbert/Spec/muc_Spec.swift index 731436a34ee739cf36dc940b27601b717016769d..6074d67af449bc0c3c86db9ede6acd3dacc5725c 100644 --- a/apps/roster/priv/macbert/Spec/muc_Spec.swift +++ b/apps/roster/priv/macbert/Spec/muc_Spec.swift @@ -1,5 +1,3 @@ func get_muc() -> Model { return Model(value:Tuple(name:"muc",body:[ - Model(value:Chain(types:[ - Model(value:List(constant:"")), - Model(value:Binary())]))]))} + Model(value:Binary())]))} diff --git a/apps/roster/priv/macbert/Spec/p2p_Spec.swift b/apps/roster/priv/macbert/Spec/p2p_Spec.swift index 90aad2eeb70b06605167fe631cf3a84a491427b5..b175ecd9fff70d9a8f578d5d18f91590347f632c 100644 --- a/apps/roster/priv/macbert/Spec/p2p_Spec.swift +++ b/apps/roster/priv/macbert/Spec/p2p_Spec.swift @@ -1,8 +1,4 @@ func get_p2p() -> Model { return Model(value:Tuple(name:"p2p",body:[ - Model(value:Chain(types:[ - Model(value:List(constant:"")), - Model(value:Binary())])), - Model(value:Chain(types:[ - Model(value:List(constant:"")), - Model(value:Binary())]))]))} + Model(value:Binary()), + Model(value:Binary())]))} diff --git a/apps/roster/priv/macbert/Spec/process_Spec.swift b/apps/roster/priv/macbert/Spec/process_Spec.swift index 98684e5ab51555233c723e4d2d72bd50291c1f0d..e9c7e0513c86beaadb34a4057ca0b7d5129a333e 100644 --- a/apps/roster/priv/macbert/Spec/process_Spec.swift +++ b/apps/roster/priv/macbert/Spec/process_Spec.swift @@ -44,7 +44,10 @@ func get_process() -> Model { Model(value:Binary())])), Model(value:Chain(types:[ Model(value:List(constant:"")), - Model(value:Binary())])), + Model(value:Tuple(name:nil,body:[ + Model(value:Chain(types:[Model(value:Tuple()),Model(value:Atom()),Model(value:Binary()),Model(value:Number()),Model(value:List(constant:""))])), + Model(value:Chain(types:[Model(value:Tuple()),Model(value:Atom()),Model(value:Binary()),Model(value:Number()),Model(value:List(constant:""))])), + Model(value:Chain(types:[Model(value:Tuple()),Model(value:Atom()),Model(value:Binary()),Model(value:Number()),Model(value:List(constant:""))]))]))])), Model(value:Chain(types:[ Model(value:List(constant:"")), Model(value:Atom())])), diff --git a/apps/roster/priv/macbert/Spec/receiveTask_Spec.swift b/apps/roster/priv/macbert/Spec/receiveTask_Spec.swift index f9c67010ef8a84bf2d155036f04ac2a93ebd32c4..81cf3c76b7835fc80a4b1d446543c94d49181d68 100644 --- a/apps/roster/priv/macbert/Spec/receiveTask_Spec.swift +++ b/apps/roster/priv/macbert/Spec/receiveTask_Spec.swift @@ -3,7 +3,8 @@ func get_receiveTask() -> Model { Model(value:Chain(types:[ Model(value:List(constant:"")), Model(value:Atom())])), - Model(value:Binary()), Model(value:Chain(types:[ Model(value:List(constant:"")), - Model(value:Atom())]))]))} + Model(value:Atom())])), + Model(value:List(constant:nil, model:Model(value:Tuple()))), + Model(value:Binary())]))} diff --git a/apps/roster/priv/macbert/Spec/sequenceFlow_Spec.swift b/apps/roster/priv/macbert/Spec/sequenceFlow_Spec.swift index 33279f7f53e0c9b535267ffd692ac53dda189340..bc21a8ebe79531daaa60d59800ca74f051b0d148 100644 --- a/apps/roster/priv/macbert/Spec/sequenceFlow_Spec.swift +++ b/apps/roster/priv/macbert/Spec/sequenceFlow_Spec.swift @@ -5,4 +5,5 @@ func get_sequenceFlow() -> Model { Model(value:Atom())])), Model(value:Chain(types:[ Model(value:List(constant:"")), - Model(value:Atom())]))]))} + Model(value:Atom()), + Model(value:List(constant:nil, model:Model(value:Atom())))]))]))} diff --git a/apps/roster/priv/macbert/Spec/serviceTask_Spec.swift b/apps/roster/priv/macbert/Spec/serviceTask_Spec.swift index 69d117a2979c3e187cf978d8c9f32f20b9de3b25..586517f5d06ebe8cb75c670fc9466c28586a2a7f 100644 --- a/apps/roster/priv/macbert/Spec/serviceTask_Spec.swift +++ b/apps/roster/priv/macbert/Spec/serviceTask_Spec.swift @@ -3,7 +3,8 @@ func get_serviceTask() -> Model { Model(value:Chain(types:[ Model(value:List(constant:"")), Model(value:Atom())])), - Model(value:Binary()), Model(value:Chain(types:[ Model(value:List(constant:"")), - Model(value:Atom())]))]))} + Model(value:Atom())])), + Model(value:List(constant:nil, model:Model(value:Tuple()))), + Model(value:Binary())]))} diff --git a/apps/roster/priv/macbert/Spec/task_Spec.swift b/apps/roster/priv/macbert/Spec/task_Spec.swift index 655fddf2eb674a9766c2b00fc3cf7e5f96ede28c..d1734eecb0d6c0105ea9181c8e41699f72eff987 100644 --- a/apps/roster/priv/macbert/Spec/task_Spec.swift +++ b/apps/roster/priv/macbert/Spec/task_Spec.swift @@ -3,7 +3,8 @@ func get_task() -> Model { Model(value:Chain(types:[ Model(value:List(constant:"")), Model(value:Atom())])), - Model(value:Binary()), Model(value:Chain(types:[ Model(value:List(constant:"")), - Model(value:Atom())]))]))} + Model(value:Atom())])), + Model(value:List(constant:nil, model:Model(value:Tuple()))), + Model(value:Binary())]))} diff --git a/apps/roster/priv/macbert/Spec/timeoutEvent_Spec.swift b/apps/roster/priv/macbert/Spec/timeoutEvent_Spec.swift index 83645b3cd74ba2051fdc8ae4c13f1752daaf87a6..c4b03fdc2d93af75d73f1f061b0d79ed8a7e8777 100644 --- a/apps/roster/priv/macbert/Spec/timeoutEvent_Spec.swift +++ b/apps/roster/priv/macbert/Spec/timeoutEvent_Spec.swift @@ -3,6 +3,10 @@ func get_timeoutEvent() -> Model { Model(value:Chain(types:[ Model(value:List(constant:"")), Model(value:Atom())])), + Model(value:Chain(types:[ + Model(value:List(constant:"")), + Model(value:Atom())])), + Model(value:List(constant:nil, model:Model(value:Tuple()))), Model(value:Chain(types:[ Model(value:List(constant:"")), Model(value:Binary())])), @@ -22,7 +26,4 @@ func get_timeoutEvent() -> Model { Model(value:Binary())])), Model(value:Chain(types:[ Model(value:List(constant:"")), - Model(value:Binary())])), - Model(value:Chain(types:[ - Model(value:List(constant:"")), - Model(value:Atom())]))]))} + Model(value:Binary())]))]))} diff --git a/apps/roster/priv/macbert/Spec/userTask_Spec.swift b/apps/roster/priv/macbert/Spec/userTask_Spec.swift index f31adb76a84ea5819f472f3c2cbe835451a08650..2912b0912c507a1cc61fb472e53b9cef82b54805 100644 --- a/apps/roster/priv/macbert/Spec/userTask_Spec.swift +++ b/apps/roster/priv/macbert/Spec/userTask_Spec.swift @@ -3,7 +3,8 @@ func get_userTask() -> Model { Model(value:Chain(types:[ Model(value:List(constant:"")), Model(value:Atom())])), - Model(value:Binary()), Model(value:Chain(types:[ Model(value:List(constant:"")), - Model(value:Atom())]))]))} + Model(value:Atom())])), + Model(value:List(constant:nil, model:Model(value:Tuple()))), + Model(value:Binary())]))} diff --git a/apps/roster/priv/macbert/json-bert.js b/apps/roster/priv/macbert/json-bert.js index d9459f327878f81b9aef0c1f54708e47995b8c01..1d7a3225a95205a66056e02727ecf069cc247096 100644 --- a/apps/roster/priv/macbert/json-bert.js +++ b/apps/roster/priv/macbert/json-bert.js @@ -1,5 +1,5 @@ function clean(r) { for(var k in r) if(!r[k]) delete r[k]; return r; } -function check_len(x) { try { return (eval('len'+utf8_dec(x.v[0].v))() == x.v.length) ? true : false } +function check_len(x) { try { return (eval('len'+utf8_arr(x.v[0].v))() == x.v.length) ? true : false } catch (e) { return false; } } function scalar(data) { @@ -18,7 +18,7 @@ function decode(x) { } else if (x.t == 108) { var r = []; x.v.forEach(function(y) { r.push(decode(y)) }); return r; } else if (x.t == 109) { - return utf8_dec(x.v); + return utf8_arr(x.v); } else if (x.t == 104 && check_len(x)) { return eval('dec'+x.v[0].v)(x); } else if (x.t == 104) { @@ -39,6 +39,673 @@ function encode(x) { } else return scalar(x); } +function encwriter(d) { + var tup = atom('writer'); + var id = 'id' in d && d.id ? encode(d.id) : nil(); + var count = 'count' in d && d.count ? number(d.count) : nil(); + var cache = 'cache' in d && d.cache ? encode(d.cache) : nil(); + var args = 'args' in d && d.args ? encode(d.args) : nil(); + var first = 'first' in d && d.first ? encode(d.first) : nil(); + return tuple(tup,id,count,cache,args,first); } + +function lenwriter() { return 6; } +function decwriter(d) { + var r={}; r.tup = 'writer'; + r.id = d && d.v[1] ? decode(d.v[1]) : undefined; + r.count = d && d.v[2] ? d.v[2].v : undefined; + r.cache = d && d.v[3] ? decode(d.v[3]) : undefined; + r.args = d && d.v[4] ? decode(d.v[4]) : undefined; + r.first = d && d.v[5] ? decode(d.v[5]) : undefined; + return clean(r); } + +function encreader(d) { + var tup = atom('reader'); + var id = 'id' in d && d.id ? encode(d.id) : nil(); + var pos = 'pos' in d && d.pos ? number(d.pos) : nil(); + var cache = 'cache' in d && d.cache ? number(d.cache) : nil(); + var args = 'args' in d && d.args ? encode(d.args) : nil(); + var feed = 'feed' in d && d.feed ? encode(d.feed) : nil(); + var dir = 'dir' in d && d.dir ? encode(d.dir) : nil(); + return tuple(tup,id,pos,cache,args,feed,dir); } + +function lenreader() { return 7; } +function decreader(d) { + var r={}; r.tup = 'reader'; + r.id = d && d.v[1] ? decode(d.v[1]) : undefined; + r.pos = d && d.v[2] ? d.v[2].v : undefined; + r.cache = d && d.v[3] ? d.v[3].v : undefined; + r.args = d && d.v[4] ? decode(d.v[4]) : undefined; + r.feed = d && d.v[5] ? decode(d.v[5]) : undefined; + r.dir = d && d.v[6] ? decode(d.v[6]) : undefined; + return clean(r); } + +function enccur(d) { + var tup = atom('cur'); + var id = 'id' in d && d.id ? encode(d.id) : nil(); + var top = 'top' in d && d.top ? number(d.top) : nil(); + var bot = 'bot' in d && d.bot ? number(d.bot) : nil(); + var dir = 'dir' in d && d.dir ? encode(d.dir) : nil(); + var reader = 'reader' in d && d.reader ? encode(d.reader) : nil(); + var writer = 'writer' in d && d.writer ? encode(d.writer) : nil(); + var left = 'left' in d && d.left ? encode(d.left) : nil(); + var right = 'right' in d && d.right ? encode(d.right) : nil(); + var args = []; if ('args' in d && d.args) + { d.args.forEach(function(x){ + args.push(encode(x))}); + args={t:108,v:args}; } else { args = nil() }; + var money = 'money' in d && d.money ? encode(d.money) : nil(); + var status = 'status' in d && d.status ? encode(d.status) : nil(); + return tuple(tup,id,top,bot,dir,reader,writer,left,right,args,money,status); } + +function lencur() { return 12; } +function deccur(d) { + var r={}; r.tup = 'cur'; + r.id = d && d.v[1] ? decode(d.v[1]) : undefined; + r.top = d && d.v[2] ? d.v[2].v : undefined; + r.bot = d && d.v[3] ? d.v[3].v : undefined; + r.dir = d && d.v[4] ? decode(d.v[4]) : undefined; + r.reader = d && d.v[5] ? decode(d.v[5]) : undefined; + r.writer = d && d.v[6] ? decode(d.v[6]) : undefined; + r.left = d && d.v[7] ? decode(d.v[7]) : undefined; + r.right = d && d.v[8] ? decode(d.v[8]) : undefined; + r.args = []; + (d && d.v[9] && d.v[9].v) ? + d.v[9].v.forEach(function(x){r.args.push(decode(x))}) : + r.args = undefined; + r.money = d && d.v[10] ? decode(d.v[10]) : undefined; + r.status = d && d.v[11] ? decode(d.v[11]) : undefined; + return clean(r); } + +function enciter(d) { + var tup = atom('iter'); + var id = 'id' in d && d.id ? number(d.id) : nil(); + var container = 'container' in d && d.container ? atom(d.container) : nil(); + var feed = 'feed' in d && d.feed ? encode(d.feed) : nil(); + var next = 'next' in d && d.next ? number(d.next) : nil(); + var prev = 'prev' in d && d.prev ? number(d.prev) : nil(); + return tuple(tup,id,container,feed,next,prev); } + +function leniter() { return 6; } +function deciter(d) { + var r={}; r.tup = 'iter'; + r.id = d && d.v[1] ? d.v[1].v : undefined; + r.container = d && d.v[2] ? d.v[2].v : undefined; + r.feed = d && d.v[3] ? decode(d.v[3]) : undefined; + r.next = d && d.v[4] ? d.v[4].v : undefined; + r.prev = d && d.v[5] ? d.v[5].v : undefined; + return clean(r); } + +function enccontainer(d) { + var tup = atom('container'); + var id = 'id' in d && d.id ? number(d.id) : nil(); + var top = 'top' in d && d.top ? number(d.top) : nil(); + var rear = 'rear' in d && d.rear ? number(d.rear) : nil(); + var count = 'count' in d && d.count ? number(d.count) : nil(); + return tuple(tup,id,top,rear,count); } + +function lencontainer() { return 5; } +function deccontainer(d) { + var r={}; r.tup = 'container'; + r.id = d && d.v[1] ? d.v[1].v : undefined; + r.top = d && d.v[2] ? d.v[2].v : undefined; + r.rear = d && d.v[3] ? d.v[3].v : undefined; + r.count = d && d.v[4] ? d.v[4].v : undefined; + return clean(r); } + +function enciterator(d) { + var tup = atom('iterator'); + var id = 'id' in d && d.id ? number(d.id) : nil(); + var container = 'container' in d && d.container ? atom(d.container) : nil(); + var feed_id = 'feed_id' in d && d.feed_id ? encode(d.feed_id) : nil(); + var prev = 'prev' in d && d.prev ? number(d.prev) : nil(); + var next = 'next' in d && d.next ? number(d.next) : nil(); + var feeds = []; if ('feeds' in d && d.feeds) + { d.feeds.forEach(function(x){ + feeds.push(encode(x))}); + feeds={t:108,v:feeds}; } else { feeds = nil() }; + return tuple(tup,id,container,feed_id,prev,next,feeds); } + +function leniterator() { return 7; } +function deciterator(d) { + var r={}; r.tup = 'iterator'; + r.id = d && d.v[1] ? d.v[1].v : undefined; + r.container = d && d.v[2] ? d.v[2].v : undefined; + r.feed_id = d && d.v[3] ? decode(d.v[3]) : undefined; + r.prev = d && d.v[4] ? d.v[4].v : undefined; + r.next = d && d.v[5] ? d.v[5].v : undefined; + r.feeds = []; + (d && d.v[6] && d.v[6].v) ? + d.v[6].v.forEach(function(x){r.feeds.push(decode(x))}) : + r.feeds = undefined; + return clean(r); } + +function enclog(d) { + var tup = atom('log'); + var id = 'id' in d && d.id ? number(d.id) : nil(); + var top = 'top' in d && d.top ? number(d.top) : nil(); + var rear = 'rear' in d && d.rear ? number(d.rear) : nil(); + var count = 'count' in d && d.count ? number(d.count) : nil(); + return tuple(tup,id,top,rear,count); } + +function lenlog() { return 5; } +function declog(d) { + var r={}; r.tup = 'log'; + r.id = d && d.v[1] ? d.v[1].v : undefined; + r.top = d && d.v[2] ? d.v[2].v : undefined; + r.rear = d && d.v[3] ? d.v[3].v : undefined; + r.count = d && d.v[4] ? d.v[4].v : undefined; + return clean(r); } + +function encoperation(d) { + var tup = atom('operation'); + var id = 'id' in d && d.id ? number(d.id) : nil(); + var container = 'container' in d && d.container ? atom(d.container) : nil(); + var feed_id = 'feed_id' in d && d.feed_id ? encode(d.feed_id) : nil(); + var prev = 'prev' in d && d.prev ? number(d.prev) : nil(); + var next = 'next' in d && d.next ? number(d.next) : nil(); + var feeds = []; if ('feeds' in d && d.feeds) + { d.feeds.forEach(function(x){ + feeds.push(encode(x))}); + feeds={t:108,v:feeds}; } else { feeds = nil() }; + var body = 'body' in d && d.body ? encode(d.body) : nil(); + var name = 'name' in d && d.name ? encode(d.name) : nil(); + var status = 'status' in d && d.status ? encode(d.status) : nil(); + return tuple(tup,id,container,feed_id,prev,next,feeds,body,name,status); } + +function lenoperation() { return 10; } +function decoperation(d) { + var r={}; r.tup = 'operation'; + r.id = d && d.v[1] ? d.v[1].v : undefined; + r.container = d && d.v[2] ? d.v[2].v : undefined; + r.feed_id = d && d.v[3] ? decode(d.v[3]) : undefined; + r.prev = d && d.v[4] ? d.v[4].v : undefined; + r.next = d && d.v[5] ? d.v[5].v : undefined; + r.feeds = []; + (d && d.v[6] && d.v[6].v) ? + d.v[6].v.forEach(function(x){r.feeds.push(decode(x))}) : + r.feeds = undefined; + r.body = d && d.v[7] ? decode(d.v[7]) : undefined; + r.name = d && d.v[8] ? decode(d.v[8]) : undefined; + r.status = d && d.v[9] ? decode(d.v[9]) : undefined; + return clean(r); } + +function enckvs(d) { + var tup = atom('kvs'); + var mod = 'mod' in d && d.mod ? encode(d.mod) : nil(); + return tuple(tup,mod); } + +function lenkvs() { return 2; } +function deckvs(d) { + var r={}; r.tup = 'kvs'; + r.mod = d && d.v[1] ? decode(d.v[1]) : undefined; + return clean(r); } + +function encfeed(d) { + var tup = atom('feed'); + var id = 'id' in d && d.id ? number(d.id) : nil(); + var top = 'top' in d && d.top ? number(d.top) : nil(); + var rear = 'rear' in d && d.rear ? number(d.rear) : nil(); + var count = 'count' in d && d.count ? number(d.count) : nil(); + var aclver = 'aclver' in d && d.aclver ? encode(d.aclver) : nil(); + return tuple(tup,id,top,rear,count,aclver); } + +function lenfeed() { return 6; } +function decfeed(d) { + var r={}; r.tup = 'feed'; + r.id = d && d.v[1] ? d.v[1].v : undefined; + r.top = d && d.v[2] ? d.v[2].v : undefined; + r.rear = d && d.v[3] ? d.v[3].v : undefined; + r.count = d && d.v[4] ? d.v[4].v : undefined; + r.aclver = d && d.v[5] ? decode(d.v[5]) : undefined; + return clean(r); } + +function enctask(d) { + var tup = atom('task'); + var name = 'name' in d && d.name ? atom(d.name) : nil(); + var module = 'module' in d && d.module ? atom(d.module) : nil(); + var prompt = []; if ('prompt' in d && d.prompt) + { d.prompt.forEach(function(x){ + prompt.push(encode(x))}); + prompt={t:108,v:prompt}; } else { prompt = nil() }; + var roles = 'roles' in d && d.roles ? bin(d.roles) : nil(); + return tuple(tup,name,module,prompt,roles); } + +function lentask() { return 5; } +function dectask(d) { + var r={}; r.tup = 'task'; + r.name = d && d.v[1] ? d.v[1].v : undefined; + r.module = d && d.v[2] ? d.v[2].v : undefined; + r.prompt = []; + (d && d.v[3] && d.v[3].v) ? + d.v[3].v.forEach(function(x){r.prompt.push(decode(x))}) : + r.prompt = undefined; + r.roles = d && d.v[4] ? utf8_arr(d.v[4].v) : undefined; + return clean(r); } + +function encuserTask(d) { + var tup = atom('userTask'); + var name = 'name' in d && d.name ? atom(d.name) : nil(); + var module = 'module' in d && d.module ? atom(d.module) : nil(); + var prompt = []; if ('prompt' in d && d.prompt) + { d.prompt.forEach(function(x){ + prompt.push(encode(x))}); + prompt={t:108,v:prompt}; } else { prompt = nil() }; + var roles = 'roles' in d && d.roles ? bin(d.roles) : nil(); + return tuple(tup,name,module,prompt,roles); } + +function lenuserTask() { return 5; } +function decuserTask(d) { + var r={}; r.tup = 'userTask'; + r.name = d && d.v[1] ? d.v[1].v : undefined; + r.module = d && d.v[2] ? d.v[2].v : undefined; + r.prompt = []; + (d && d.v[3] && d.v[3].v) ? + d.v[3].v.forEach(function(x){r.prompt.push(decode(x))}) : + r.prompt = undefined; + r.roles = d && d.v[4] ? utf8_arr(d.v[4].v) : undefined; + return clean(r); } + +function encserviceTask(d) { + var tup = atom('serviceTask'); + var name = 'name' in d && d.name ? atom(d.name) : nil(); + var module = 'module' in d && d.module ? atom(d.module) : nil(); + var prompt = []; if ('prompt' in d && d.prompt) + { d.prompt.forEach(function(x){ + prompt.push(encode(x))}); + prompt={t:108,v:prompt}; } else { prompt = nil() }; + var roles = 'roles' in d && d.roles ? bin(d.roles) : nil(); + return tuple(tup,name,module,prompt,roles); } + +function lenserviceTask() { return 5; } +function decserviceTask(d) { + var r={}; r.tup = 'serviceTask'; + r.name = d && d.v[1] ? d.v[1].v : undefined; + r.module = d && d.v[2] ? d.v[2].v : undefined; + r.prompt = []; + (d && d.v[3] && d.v[3].v) ? + d.v[3].v.forEach(function(x){r.prompt.push(decode(x))}) : + r.prompt = undefined; + r.roles = d && d.v[4] ? utf8_arr(d.v[4].v) : undefined; + return clean(r); } + +function encreceiveTask(d) { + var tup = atom('receiveTask'); + var name = 'name' in d && d.name ? atom(d.name) : nil(); + var module = 'module' in d && d.module ? atom(d.module) : nil(); + var prompt = []; if ('prompt' in d && d.prompt) + { d.prompt.forEach(function(x){ + prompt.push(encode(x))}); + prompt={t:108,v:prompt}; } else { prompt = nil() }; + var roles = 'roles' in d && d.roles ? bin(d.roles) : nil(); + return tuple(tup,name,module,prompt,roles); } + +function lenreceiveTask() { return 5; } +function decreceiveTask(d) { + var r={}; r.tup = 'receiveTask'; + r.name = d && d.v[1] ? d.v[1].v : undefined; + r.module = d && d.v[2] ? d.v[2].v : undefined; + r.prompt = []; + (d && d.v[3] && d.v[3].v) ? + d.v[3].v.forEach(function(x){r.prompt.push(decode(x))}) : + r.prompt = undefined; + r.roles = d && d.v[4] ? utf8_arr(d.v[4].v) : undefined; + return clean(r); } + +function encmessageEvent(d) { + var tup = atom('messageEvent'); + var name = 'name' in d && d.name ? atom(d.name) : nil(); + var module = 'module' in d && d.module ? atom(d.module) : nil(); + var prompt = []; if ('prompt' in d && d.prompt) + { d.prompt.forEach(function(x){ + prompt.push(encode(x))}); + prompt={t:108,v:prompt}; } else { prompt = nil() }; + var payload = 'payload' in d && d.payload ? bin(d.payload) : nil(); + var timeout = 'timeout' in d && d.timeout ? encode(d.timeout) : nil(); + return tuple(tup,name,module,prompt,payload,timeout); } + +function lenmessageEvent() { return 6; } +function decmessageEvent(d) { + var r={}; r.tup = 'messageEvent'; + r.name = d && d.v[1] ? d.v[1].v : undefined; + r.module = d && d.v[2] ? d.v[2].v : undefined; + r.prompt = []; + (d && d.v[3] && d.v[3].v) ? + d.v[3].v.forEach(function(x){r.prompt.push(decode(x))}) : + r.prompt = undefined; + r.payload = d && d.v[4] ? utf8_arr(d.v[4].v) : undefined; + r.timeout = d && d.v[5] ? decode(d.v[5]) : undefined; + return clean(r); } + +function encboundaryEvent(d) { + var tup = atom('boundaryEvent'); + var name = 'name' in d && d.name ? atom(d.name) : nil(); + var module = 'module' in d && d.module ? atom(d.module) : nil(); + var prompt = []; if ('prompt' in d && d.prompt) + { d.prompt.forEach(function(x){ + prompt.push(encode(x))}); + prompt={t:108,v:prompt}; } else { prompt = nil() }; + var payload = 'payload' in d && d.payload ? bin(d.payload) : nil(); + var timeout = 'timeout' in d && d.timeout ? encode(d.timeout) : nil(); + var timeDate = 'timeDate' in d && d.timeDate ? bin(d.timeDate) : nil(); + var timeDuration = 'timeDuration' in d && d.timeDuration ? bin(d.timeDuration) : nil(); + var timeCycle = 'timeCycle' in d && d.timeCycle ? bin(d.timeCycle) : nil(); + return tuple(tup,name,module,prompt,payload,timeout,timeDate,timeDuration,timeCycle); } + +function lenboundaryEvent() { return 9; } +function decboundaryEvent(d) { + var r={}; r.tup = 'boundaryEvent'; + r.name = d && d.v[1] ? d.v[1].v : undefined; + r.module = d && d.v[2] ? d.v[2].v : undefined; + r.prompt = []; + (d && d.v[3] && d.v[3].v) ? + d.v[3].v.forEach(function(x){r.prompt.push(decode(x))}) : + r.prompt = undefined; + r.payload = d && d.v[4] ? utf8_arr(d.v[4].v) : undefined; + r.timeout = d && d.v[5] ? decode(d.v[5]) : undefined; + r.timeDate = d && d.v[6] ? utf8_arr(d.v[6].v) : undefined; + r.timeDuration = d && d.v[7] ? utf8_arr(d.v[7].v) : undefined; + r.timeCycle = d && d.v[8] ? utf8_arr(d.v[8].v) : undefined; + return clean(r); } + +function enctimeoutEvent(d) { + var tup = atom('timeoutEvent'); + var name = 'name' in d && d.name ? atom(d.name) : nil(); + var module = 'module' in d && d.module ? atom(d.module) : nil(); + var prompt = []; if ('prompt' in d && d.prompt) + { d.prompt.forEach(function(x){ + prompt.push(encode(x))}); + prompt={t:108,v:prompt}; } else { prompt = nil() }; + var payload = 'payload' in d && d.payload ? bin(d.payload) : nil(); + var timeout = 'timeout' in d && d.timeout ? encode(d.timeout) : nil(); + var timeDate = 'timeDate' in d && d.timeDate ? bin(d.timeDate) : nil(); + var timeDuration = 'timeDuration' in d && d.timeDuration ? bin(d.timeDuration) : nil(); + var timeCycle = 'timeCycle' in d && d.timeCycle ? bin(d.timeCycle) : nil(); + return tuple(tup,name,module,prompt,payload,timeout,timeDate,timeDuration,timeCycle); } + +function lentimeoutEvent() { return 9; } +function dectimeoutEvent(d) { + var r={}; r.tup = 'timeoutEvent'; + r.name = d && d.v[1] ? d.v[1].v : undefined; + r.module = d && d.v[2] ? d.v[2].v : undefined; + r.prompt = []; + (d && d.v[3] && d.v[3].v) ? + d.v[3].v.forEach(function(x){r.prompt.push(decode(x))}) : + r.prompt = undefined; + r.payload = d && d.v[4] ? utf8_arr(d.v[4].v) : undefined; + r.timeout = d && d.v[5] ? decode(d.v[5]) : undefined; + r.timeDate = d && d.v[6] ? utf8_arr(d.v[6].v) : undefined; + r.timeDuration = d && d.v[7] ? utf8_arr(d.v[7].v) : undefined; + r.timeCycle = d && d.v[8] ? utf8_arr(d.v[8].v) : undefined; + return clean(r); } + +function encbeginEvent(d) { + var tup = atom('beginEvent'); + var name = 'name' in d && d.name ? atom(d.name) : nil(); + var module = 'module' in d && d.module ? atom(d.module) : nil(); + var prompt = []; if ('prompt' in d && d.prompt) + { d.prompt.forEach(function(x){ + prompt.push(encode(x))}); + prompt={t:108,v:prompt}; } else { prompt = nil() }; + return tuple(tup,name,module,prompt); } + +function lenbeginEvent() { return 4; } +function decbeginEvent(d) { + var r={}; r.tup = 'beginEvent'; + r.name = d && d.v[1] ? d.v[1].v : undefined; + r.module = d && d.v[2] ? d.v[2].v : undefined; + r.prompt = []; + (d && d.v[3] && d.v[3].v) ? + d.v[3].v.forEach(function(x){r.prompt.push(decode(x))}) : + r.prompt = undefined; + return clean(r); } + +function encendEvent(d) { + var tup = atom('endEvent'); + var name = 'name' in d && d.name ? atom(d.name) : nil(); + var module = 'module' in d && d.module ? atom(d.module) : nil(); + var prompt = []; if ('prompt' in d && d.prompt) + { d.prompt.forEach(function(x){ + prompt.push(encode(x))}); + prompt={t:108,v:prompt}; } else { prompt = nil() }; + return tuple(tup,name,module,prompt); } + +function lenendEvent() { return 4; } +function decendEvent(d) { + var r={}; r.tup = 'endEvent'; + r.name = d && d.v[1] ? d.v[1].v : undefined; + r.module = d && d.v[2] ? d.v[2].v : undefined; + r.prompt = []; + (d && d.v[3] && d.v[3].v) ? + d.v[3].v.forEach(function(x){r.prompt.push(decode(x))}) : + r.prompt = undefined; + return clean(r); } + +function encsequenceFlow(d) { + var tup = atom('sequenceFlow'); + var source = 'source' in d && d.source ? atom(d.source) : nil(); + var target = 'target' in d && d.target ? encode(d.target) : nil(); + return tuple(tup,source,target); } + +function lensequenceFlow() { return 3; } +function decsequenceFlow(d) { + var r={}; r.tup = 'sequenceFlow'; + r.source = d && d.v[1] ? d.v[1].v : undefined; + r.target = d && d.v[2] ? decode(d.v[2]) : undefined; + return clean(r); } + +function enchist(d) { + var tup = atom('hist'); + var id = 'id' in d && d.id ? number(d.id) : nil(); + var container = 'container' in d && d.container ? atom(d.container) : nil(); + var feed_id = 'feed_id' in d && d.feed_id ? encode(d.feed_id) : nil(); + var prev = 'prev' in d && d.prev ? number(d.prev) : nil(); + var next = 'next' in d && d.next ? number(d.next) : nil(); + var feeds = []; if ('feeds' in d && d.feeds) + { d.feeds.forEach(function(x){ + feeds.push(encode(x))}); + feeds={t:108,v:feeds}; } else { feeds = nil() }; + var name = 'name' in d && d.name ? bin(d.name) : nil(); + var task = 'task' in d && d.task ? atom(d.task) : nil(); + var docs = []; if ('docs' in d && d.docs) + { d.docs.forEach(function(x){ + docs.push(encode(x))}); + docs={t:108,v:docs}; } else { docs = nil() }; + var time = 'time' in d && d.time ? encode(d.time) : nil(); + return tuple(tup,id,container,feed_id,prev,next,feeds,name,task,docs,time); } + +function lenhist() { return 11; } +function dechist(d) { + var r={}; r.tup = 'hist'; + r.id = d && d.v[1] ? d.v[1].v : undefined; + r.container = d && d.v[2] ? d.v[2].v : undefined; + r.feed_id = d && d.v[3] ? decode(d.v[3]) : undefined; + r.prev = d && d.v[4] ? d.v[4].v : undefined; + r.next = d && d.v[5] ? d.v[5].v : undefined; + r.feeds = []; + (d && d.v[6] && d.v[6].v) ? + d.v[6].v.forEach(function(x){r.feeds.push(decode(x))}) : + r.feeds = undefined; + r.name = d && d.v[7] ? utf8_arr(d.v[7].v) : undefined; + r.task = d && d.v[8] ? d.v[8].v : undefined; + r.docs = []; + (d && d.v[9] && d.v[9].v) ? + d.v[9].v.forEach(function(x){r.docs.push(decode(x))}) : + r.docs = undefined; + r.time = d && d.v[10] ? decode(d.v[10]) : undefined; + return clean(r); } + +function encprocess(d) { + var tup = atom('process'); + var id = 'id' in d && d.id ? number(d.id) : nil(); + var container = 'container' in d && d.container ? atom(d.container) : nil(); + var feed_id = 'feed_id' in d && d.feed_id ? encode(d.feed_id) : nil(); + var prev = 'prev' in d && d.prev ? number(d.prev) : nil(); + var next = 'next' in d && d.next ? number(d.next) : nil(); + var feeds = []; if ('feeds' in d && d.feeds) + { d.feeds.forEach(function(x){ + feeds.push(encode(x))}); + feeds={t:108,v:feeds}; } else { feeds = nil() }; + var name = 'name' in d && d.name ? bin(d.name) : nil(); + var roles = []; if ('roles' in d && d.roles) + { d.roles.forEach(function(x){ + roles.push(encode(x))}); + roles={t:108,v:roles}; } else { roles = nil() }; + var tasks = []; if ('tasks' in d && d.tasks) + { d.tasks.forEach(function(x){ + tasks.push(encode(x))}); + tasks={t:108,v:tasks}; } else { tasks = nil() }; + var events = []; if ('events' in d && d.events) + { d.events.forEach(function(x){ + events.push(encode(x))}); + events={t:108,v:events}; } else { events = nil() }; + var hist = 'hist' in d && d.hist ? encode(d.hist) : nil(); + var flows = []; if ('flows' in d && d.flows) + { d.flows.forEach(function(x){ + flows.push(encode(x))}); + flows={t:108,v:flows}; } else { flows = nil() }; + var rules = 'rules' in d && d.rules ? encode(d.rules) : nil(); + var docs = []; if ('docs' in d && d.docs) + { d.docs.forEach(function(x){ + docs.push(encode(x))}); + docs={t:108,v:docs}; } else { docs = nil() }; + var options = 'options' in d && d.options ? encode(d.options) : nil(); + var task = 'task' in d && d.task ? atom(d.task) : nil(); + var timer = 'timer' in d && d.timer ? bin(d.timer) : nil(); + var notifications = 'notifications' in d && d.notifications ? encode(d.notifications) : nil(); + var result = 'result' in d && d.result ? bin(d.result) : nil(); + var started = 'started' in d && d.started ? encode(d.started) : nil(); + var beginEvent = 'beginEvent' in d && d.beginEvent ? atom(d.beginEvent) : nil(); + var endEvent = 'endEvent' in d && d.endEvent ? atom(d.endEvent) : nil(); + return tuple(tup,id,container,feed_id,prev,next,feeds,name,roles,tasks,events, + hist,flows,rules,docs,options,task,timer,notifications,result,started,beginEvent,endEvent); } + +function lenprocess() { return 23; } +function decprocess(d) { + var r={}; r.tup = 'process'; + r.id = d && d.v[1] ? d.v[1].v : undefined; + r.container = d && d.v[2] ? d.v[2].v : undefined; + r.feed_id = d && d.v[3] ? decode(d.v[3]) : undefined; + r.prev = d && d.v[4] ? d.v[4].v : undefined; + r.next = d && d.v[5] ? d.v[5].v : undefined; + r.feeds = []; + (d && d.v[6] && d.v[6].v) ? + d.v[6].v.forEach(function(x){r.feeds.push(decode(x))}) : + r.feeds = undefined; + r.name = d && d.v[7] ? utf8_arr(d.v[7].v) : undefined; + r.roles = []; + (d && d.v[8] && d.v[8].v) ? + d.v[8].v.forEach(function(x){r.roles.push(decode(x))}) : + r.roles = undefined; + r.tasks = []; + (d && d.v[9] && d.v[9].v) ? + d.v[9].v.forEach(function(x){r.tasks.push(decode(x))}) : + r.tasks = undefined; + r.events = []; + (d && d.v[10] && d.v[10].v) ? + d.v[10].v.forEach(function(x){r.events.push(decode(x))}) : + r.events = undefined; + r.hist = d && d.v[11] ? decode(d.v[11]) : undefined; + r.flows = []; + (d && d.v[12] && d.v[12].v) ? + d.v[12].v.forEach(function(x){r.flows.push(decode(x))}) : + r.flows = undefined; + r.rules = d && d.v[13] ? decode(d.v[13]) : undefined; + r.docs = []; + (d && d.v[14] && d.v[14].v) ? + d.v[14].v.forEach(function(x){r.docs.push(decode(x))}) : + r.docs = undefined; + r.options = d && d.v[15] ? decode(d.v[15]) : undefined; + r.task = d && d.v[16] ? d.v[16].v : undefined; + r.timer = d && d.v[17] ? utf8_arr(d.v[17].v) : undefined; + r.notifications = d && d.v[18] ? decode(d.v[18]) : undefined; + r.result = d && d.v[19] ? utf8_arr(d.v[19].v) : undefined; + r.started = d && d.v[20] ? decode(d.v[20]) : undefined; + r.beginEvent = d && d.v[21] ? d.v[21].v : undefined; + r.endEvent = d && d.v[22] ? d.v[22].v : undefined; + return clean(r); } + +function enccomplete(d) { + var tup = atom('complete'); + var id = 'id' in d && d.id ? number(d.id) : nil(); + return tuple(tup,id); } + +function lencomplete() { return 2; } +function deccomplete(d) { + var r={}; r.tup = 'complete'; + r.id = d && d.v[1] ? d.v[1].v : undefined; + return clean(r); } + +function encproc(d) { + var tup = atom('proc'); + var id = 'id' in d && d.id ? number(d.id) : nil(); + return tuple(tup,id); } + +function lenproc() { return 2; } +function decproc(d) { + var r={}; r.tup = 'proc'; + r.id = d && d.v[1] ? d.v[1].v : undefined; + return clean(r); } + +function encload(d) { + var tup = atom('load'); + var id = 'id' in d && d.id ? number(d.id) : nil(); + return tuple(tup,id); } + +function lenload() { return 2; } +function decload(d) { + var r={}; r.tup = 'load'; + r.id = d && d.v[1] ? d.v[1].v : undefined; + return clean(r); } + +function enchisto(d) { + var tup = atom('histo'); + var id = 'id' in d && d.id ? number(d.id) : nil(); + return tuple(tup,id); } + +function lenhisto() { return 2; } +function dechisto(d) { + var r={}; r.tup = 'histo'; + r.id = d && d.v[1] ? d.v[1].v : undefined; + return clean(r); } + +function enccreate(d) { + var tup = atom('create'); + var proc = 'proc' in d && d.proc ? encode(d.proc) : nil(); + var docs = []; if ('docs' in d && d.docs) + { d.docs.forEach(function(x){ + docs.push(encode(x))}); + docs={t:108,v:docs}; } else { docs = nil() }; + return tuple(tup,proc,docs); } + +function lencreate() { return 3; } +function deccreate(d) { + var r={}; r.tup = 'create'; + r.proc = d && d.v[1] ? decode(d.v[1]) : undefined; + r.docs = []; + (d && d.v[2] && d.v[2].v) ? + d.v[2].v.forEach(function(x){r.docs.push(decode(x))}) : + r.docs = undefined; + return clean(r); } + +function encamend(d) { + var tup = atom('amend'); + var id = 'id' in d && d.id ? number(d.id) : nil(); + var docs = []; if ('docs' in d && d.docs) + { d.docs.forEach(function(x){ + docs.push(encode(x))}); + docs={t:108,v:docs}; } else { docs = nil() }; + return tuple(tup,id,docs); } + +function lenamend() { return 3; } +function decamend(d) { + var r={}; r.tup = 'amend'; + r.id = d && d.v[1] ? d.v[1].v : undefined; + r.docs = []; + (d && d.v[2] && d.v[2].v) ? + d.v[2].v.forEach(function(x){r.docs.push(decode(x))}) : + r.docs = undefined; + return clean(r); } + function encchain(d) { var tup = atom('chain'); var id = 'id' in d && d.id ? number(d.id) : nil(); @@ -74,11 +741,11 @@ function lenpush() { return 7; } function decpush(d) { var r={}; r.tup = 'push'; r.model = d && d.v[1] ? decode(d.v[1]) : undefined; - r.type = d && d.v[2] ? utf8_dec(d.v[2].v) : undefined; - r.title = d && d.v[3] ? utf8_dec(d.v[3].v) : undefined; - r.alert = d && d.v[4] ? utf8_dec(d.v[4].v) : undefined; + r.type = d && d.v[2] ? utf8_arr(d.v[2].v) : undefined; + r.title = d && d.v[3] ? utf8_arr(d.v[3].v) : undefined; + r.alert = d && d.v[4] ? utf8_arr(d.v[4].v) : undefined; r.badge = d && d.v[5] ? d.v[5].v : undefined; - r.sound = d && d.v[6] ? utf8_dec(d.v[6].v) : undefined; + r.sound = d && d.v[6] ? utf8_arr(d.v[6].v) : undefined; return clean(r); } function encSearch(d) { @@ -87,7 +754,10 @@ function encSearch(d) { var ref = 'ref' in d && d.ref ? bin(d.ref) : nil(); var field = 'field' in d && d.field ? bin(d.field) : nil(); var type = 'type' in d && d.type ? atom(d.type) : nil(); - var value = 'value' in d && d.value ? encode(d.value) : nil(); + var value = []; if ('value' in d && d.value) + { d.value.forEach(function(x){ + value.push(encode(x))}); + value={t:108,v:value}; } else { value = nil() }; var status = 'status' in d && d.status ? atom(d.status) : nil(); return tuple(tup,id,ref,field,type,value,status); } @@ -95,10 +765,13 @@ function lenSearch() { return 7; } function decSearch(d) { var r={}; r.tup = 'Search'; r.id = d && d.v[1] ? d.v[1].v : undefined; - r.ref = d && d.v[2] ? utf8_dec(d.v[2].v) : undefined; - r.field = d && d.v[3] ? utf8_dec(d.v[3].v) : undefined; + r.ref = d && d.v[2] ? utf8_arr(d.v[2].v) : undefined; + r.field = d && d.v[3] ? utf8_arr(d.v[3].v) : undefined; r.type = d && d.v[4] ? decode(d.v[4]) : undefined; - r.value = d && d.v[5] ? decode(d.v[5]) : undefined; + r.value = []; + (d && d.v[5] && d.v[5].v) ? + d.v[5].v.forEach(function(x){r.value.push(decode(x))}) : + r.value = undefined; r.status = d && d.v[6] ? decode(d.v[6]) : undefined; return clean(r); } @@ -111,8 +784,8 @@ function encp2p(d) { function lenp2p() { return 3; } function decp2p(d) { var r={}; r.tup = 'p2p'; - r.from = d && d.v[1] ? utf8_dec(d.v[1].v) : undefined; - r.to = d && d.v[2] ? utf8_dec(d.v[2].v) : undefined; + r.from = d && d.v[1] ? utf8_arr(d.v[1].v) : undefined; + r.to = d && d.v[2] ? utf8_arr(d.v[2].v) : undefined; return clean(r); } function encmuc(d) { @@ -123,7 +796,7 @@ function encmuc(d) { function lenmuc() { return 2; } function decmuc(d) { var r={}; r.tup = 'muc'; - r.name = d && d.v[1] ? utf8_dec(d.v[1].v) : undefined; + r.name = d && d.v[1] ? utf8_arr(d.v[1].v) : undefined; return clean(r); } function encmqi(d) { @@ -137,7 +810,7 @@ function lenmqi() { return 4; } function decmqi(d) { var r={}; r.tup = 'mqi'; r.feed_id = d && d.v[1] ? decode(d.v[1]) : undefined; - r.query = d && d.v[2] ? utf8_dec(d.v[2].v) : undefined; + r.query = d && d.v[2] ? utf8_arr(d.v[2].v) : undefined; r.status = d && d.v[3] ? decode(d.v[3]) : undefined; return clean(r); } @@ -152,10 +825,10 @@ function encFeature(d) { function lenFeature() { return 5; } function decFeature(d) { var r={}; r.tup = 'Feature'; - r.id = d && d.v[1] ? utf8_dec(d.v[1].v) : undefined; - r.key = d && d.v[2] ? utf8_dec(d.v[2].v) : undefined; - r.value = d && d.v[3] ? utf8_dec(d.v[3].v) : undefined; - r.group = d && d.v[4] ? utf8_dec(d.v[4].v) : undefined; + r.id = d && d.v[1] ? utf8_arr(d.v[1].v) : undefined; + r.key = d && d.v[2] ? utf8_arr(d.v[2].v) : undefined; + r.value = d && d.v[3] ? utf8_arr(d.v[3].v) : undefined; + r.group = d && d.v[4] ? utf8_arr(d.v[4].v) : undefined; return clean(r); } function encService(d) { @@ -172,11 +845,11 @@ function encService(d) { function lenService() { return 8; } function decService(d) { var r={}; r.tup = 'Service'; - r.id = d && d.v[1] ? utf8_dec(d.v[1].v) : undefined; + r.id = d && d.v[1] ? utf8_arr(d.v[1].v) : undefined; r.type = d && d.v[2] ? decode(d.v[2]) : undefined; r.data = d && d.v[3] ? decode(d.v[3]) : undefined; - r.login = d && d.v[4] ? utf8_dec(d.v[4].v) : undefined; - r.password = d && d.v[5] ? utf8_dec(d.v[5].v) : undefined; + r.login = d && d.v[4] ? utf8_arr(d.v[4].v) : undefined; + r.password = d && d.v[5] ? utf8_arr(d.v[5].v) : undefined; r.expiration = d && d.v[6] ? d.v[6].v : undefined; r.status = d && d.v[7] ? decode(d.v[7]) : undefined; return clean(r); } @@ -224,11 +897,11 @@ function decMember(d) { (d && d.v[6] && d.v[6].v) ? d.v[6].v.forEach(function(x){r.feeds.push(decode(x))}) : r.feeds = undefined; - r.phone_id = d && d.v[7] ? utf8_dec(d.v[7].v) : undefined; - r.avatar = d && d.v[8] ? utf8_dec(d.v[8].v) : undefined; - r.names = d && d.v[9] ? utf8_dec(d.v[9].v) : undefined; - r.surnames = d && d.v[10] ? utf8_dec(d.v[10].v) : undefined; - r.alias = d && d.v[11] ? utf8_dec(d.v[11].v) : undefined; + r.phone_id = d && d.v[7] ? utf8_arr(d.v[7].v) : undefined; + r.avatar = d && d.v[8] ? utf8_arr(d.v[8].v) : undefined; + r.names = d && d.v[9] ? utf8_arr(d.v[9].v) : undefined; + r.surnames = d && d.v[10] ? utf8_arr(d.v[10].v) : undefined; + r.alias = d && d.v[11] ? utf8_arr(d.v[11].v) : undefined; r.reader = d && d.v[12] ? d.v[12].v : undefined; r.update = d && d.v[13] ? d.v[13].v : undefined; r.settings = []; @@ -258,10 +931,10 @@ function encDesc(d) { function lenDesc() { return 6; } function decDesc(d) { var r={}; r.tup = 'Desc'; - r.id = d && d.v[1] ? utf8_dec(d.v[1].v) : undefined; - r.mime = d && d.v[2] ? utf8_dec(d.v[2].v) : undefined; - r.payload = d && d.v[3] ? utf8_dec(d.v[3].v) : undefined; - r.parentid = d && d.v[4] ? utf8_dec(d.v[4].v) : undefined; + r.id = d && d.v[1] ? utf8_arr(d.v[1].v) : undefined; + r.mime = d && d.v[2] ? utf8_arr(d.v[2].v) : undefined; + r.payload = d && d.v[3] ? utf8_arr(d.v[3].v) : undefined; + r.parentid = d && d.v[4] ? utf8_arr(d.v[4].v) : undefined; r.data = []; (d && d.v[5] && d.v[5].v) ? d.v[5].v.forEach(function(x){r.data.push(decode(x))}) : @@ -291,13 +964,13 @@ function lenStickerPack() { return 10; } function decStickerPack(d) { var r={}; r.tup = 'StickerPack'; r.id = d && d.v[1] ? d.v[1].v : undefined; - r.name = d && d.v[2] ? utf8_dec(d.v[2].v) : undefined; + r.name = d && d.v[2] ? utf8_arr(d.v[2].v) : undefined; r.keywords = []; (d && d.v[3] && d.v[3].v) ? d.v[3].v.forEach(function(x){r.keywords.push(decode(x))}) : r.keywords = undefined; - r.description = d && d.v[4] ? utf8_dec(d.v[4].v) : undefined; - r.author = d && d.v[5] ? utf8_dec(d.v[5].v) : undefined; + r.description = d && d.v[4] ? utf8_arr(d.v[4].v) : undefined; + r.author = d && d.v[5] ? utf8_arr(d.v[5].v) : undefined; r.stickers = []; (d && d.v[6] && d.v[6].v) ? d.v[6].v.forEach(function(x){r.stickers.push(decode(x))}) : @@ -351,9 +1024,9 @@ function decMessage(d) { r.feed_id = d && d.v[3] ? decode(d.v[3]) : undefined; r.prev = d && d.v[4] ? d.v[4].v : undefined; r.next = d && d.v[5] ? d.v[5].v : undefined; - r.msg_id = d && d.v[6] ? utf8_dec(d.v[6].v) : undefined; - r.from = d && d.v[7] ? utf8_dec(d.v[7].v) : undefined; - r.to = d && d.v[8] ? utf8_dec(d.v[8].v) : undefined; + r.msg_id = d && d.v[6] ? utf8_arr(d.v[6].v) : undefined; + r.from = d && d.v[7] ? utf8_arr(d.v[7].v) : undefined; + r.to = d && d.v[8] ? utf8_arr(d.v[8].v) : undefined; r.created = d && d.v[9] ? d.v[9].v : undefined; r.files = []; (d && d.v[10] && d.v[10].v) ? @@ -383,19 +1056,17 @@ function encLink(d) { var tup = atom('Link'); var id = 'id' in d && d.id ? bin(d.id) : nil(); var name = 'name' in d && d.name ? bin(d.name) : nil(); - var room_id = 'room_id' in d && d.room_id ? bin(d.room_id) : nil(); - var created = 'created' in d && d.created ? number(d.created) : nil(); + var type = 'type' in d && d.type ? atom(d.type) : nil(); var status = 'status' in d && d.status ? atom(d.status) : nil(); - return tuple(tup,id,name,room_id,created,status); } + return tuple(tup,id,name,type,status); } -function lenLink() { return 6; } +function lenLink() { return 5; } function decLink(d) { var r={}; r.tup = 'Link'; - r.id = d && d.v[1] ? utf8_dec(d.v[1].v) : undefined; - r.name = d && d.v[2] ? utf8_dec(d.v[2].v) : undefined; - r.room_id = d && d.v[3] ? utf8_dec(d.v[3].v) : undefined; - r.created = d && d.v[4] ? d.v[4].v : undefined; - r.status = d && d.v[5] ? decode(d.v[5]) : undefined; + r.id = d && d.v[1] ? utf8_arr(d.v[1].v) : undefined; + r.name = d && d.v[2] ? utf8_arr(d.v[2].v) : undefined; + r.type = d && d.v[3] ? decode(d.v[3]) : undefined; + r.status = d && d.v[4] ? decode(d.v[4]) : undefined; return clean(r); } function encRoom(d) { @@ -445,13 +1116,13 @@ function encRoom(d) { function lenRoom() { return 19; } function decRoom(d) { var r={}; r.tup = 'Room'; - r.id = d && d.v[1] ? utf8_dec(d.v[1].v) : undefined; - r.name = d && d.v[2] ? utf8_dec(d.v[2].v) : undefined; + r.id = d && d.v[1] ? utf8_arr(d.v[1].v) : undefined; + r.name = d && d.v[2] ? utf8_arr(d.v[2].v) : undefined; r.links = []; (d && d.v[3] && d.v[3].v) ? d.v[3].v.forEach(function(x){r.links.push(decode(x))}) : r.links = undefined; - r.description = d && d.v[4] ? utf8_dec(d.v[4].v) : undefined; + r.description = d && d.v[4] ? utf8_arr(d.v[4].v) : undefined; r.settings = []; (d && d.v[5] && d.v[5].v) ? d.v[5].v.forEach(function(x){r.settings.push(decode(x))}) : @@ -469,7 +1140,7 @@ function decRoom(d) { d.v[8].v.forEach(function(x){r.data.push(decode(x))}) : r.data = undefined; r.type = d && d.v[9] ? decode(d.v[9]) : undefined; - r.tos = d && d.v[10] ? utf8_dec(d.v[10].v) : undefined; + r.tos = d && d.v[10] ? utf8_arr(d.v[10].v) : undefined; r.tos_update = d && d.v[11] ? d.v[11].v : undefined; r.unread = d && d.v[12] ? d.v[12].v : undefined; r.mentions = []; @@ -497,9 +1168,9 @@ function encTag(d) { function lenTag() { return 5; } function decTag(d) { var r={}; r.tup = 'Tag'; - r.roster_id = d && d.v[1] ? utf8_dec(d.v[1].v) : undefined; - r.name = d && d.v[2] ? utf8_dec(d.v[2].v) : undefined; - r.color = d && d.v[3] ? utf8_dec(d.v[3].v) : undefined; + r.roster_id = d && d.v[1] ? utf8_arr(d.v[1].v) : undefined; + r.name = d && d.v[2] ? utf8_arr(d.v[2].v) : undefined; + r.color = d && d.v[3] ? utf8_arr(d.v[3].v) : undefined; r.status = d && d.v[4] ? decode(d.v[4]) : undefined; return clean(r); } @@ -520,7 +1191,7 @@ function lenStar() { return 7; } function decStar(d) { var r={}; r.tup = 'Star'; r.id = d && d.v[1] ? d.v[1].v : undefined; - r.client_id = d && d.v[2] ? utf8_dec(d.v[2].v) : undefined; + r.client_id = d && d.v[2] ? utf8_arr(d.v[2].v) : undefined; r.roster_id = d && d.v[3] ? d.v[3].v : undefined; r.message = d && d.v[4] ? decode(d.v[4]) : undefined; r.tags = []; @@ -539,7 +1210,7 @@ function encTyping(d) { function lenTyping() { return 3; } function decTyping(d) { var r={}; r.tup = 'Typing'; - r.phone_id = d && d.v[1] ? utf8_dec(d.v[1].v) : undefined; + r.phone_id = d && d.v[1] ? utf8_arr(d.v[1].v) : undefined; r.comments = d && d.v[2] ? decode(d.v[2]) : undefined; return clean(r); } @@ -550,10 +1221,7 @@ function encContact(d) { var names = 'names' in d && d.names ? bin(d.names) : nil(); var surnames = 'surnames' in d && d.surnames ? bin(d.surnames) : nil(); var nick = 'nick' in d && d.nick ? bin(d.nick) : nil(); - var reader = []; if ('reader' in d && d.reader) - { d.reader.forEach(function(x){ - reader.push(encode(x))}); - reader={t:108,v:reader}; } else { reader = nil() }; + var reader = 'reader' in d && d.reader ? encode(d.reader) : nil(); var unread = 'unread' in d && d.unread ? number(d.unread) : nil(); var last_msg = 'last_msg' in d && d.last_msg ? encode(d.last_msg) : nil(); var update = 'update' in d && d.update ? number(d.update) : nil(); @@ -574,15 +1242,12 @@ function encContact(d) { function lenContact() { return 15; } function decContact(d) { var r={}; r.tup = 'Contact'; - r.phone_id = d && d.v[1] ? utf8_dec(d.v[1].v) : undefined; - r.avatar = d && d.v[2] ? utf8_dec(d.v[2].v) : undefined; - r.names = d && d.v[3] ? utf8_dec(d.v[3].v) : undefined; - r.surnames = d && d.v[4] ? utf8_dec(d.v[4].v) : undefined; - r.nick = d && d.v[5] ? utf8_dec(d.v[5].v) : undefined; - r.reader = []; - (d && d.v[6] && d.v[6].v) ? - d.v[6].v.forEach(function(x){r.reader.push(decode(x))}) : - r.reader = undefined; + r.phone_id = d && d.v[1] ? utf8_arr(d.v[1].v) : undefined; + r.avatar = d && d.v[2] ? utf8_arr(d.v[2].v) : undefined; + r.names = d && d.v[3] ? utf8_arr(d.v[3].v) : undefined; + r.surnames = d && d.v[4] ? utf8_arr(d.v[4].v) : undefined; + r.nick = d && d.v[5] ? utf8_arr(d.v[5].v) : undefined; + r.reader = d && d.v[6] ? decode(d.v[6]) : undefined; r.unread = d && d.v[7] ? d.v[7].v : undefined; r.last_msg = d && d.v[8] ? decode(d.v[8]) : undefined; r.update = d && d.v[9] ? d.v[9].v : undefined; @@ -617,7 +1282,7 @@ function encAuth(d) { var client_id = 'client_id' in d && d.client_id ? bin(d.client_id) : nil(); var dev_key = 'dev_key' in d && d.dev_key ? bin(d.dev_key) : nil(); var user_id = 'user_id' in d && d.user_id ? bin(d.user_id) : nil(); - var phone = 'phone' in d && d.phone ? bin(d.phone) : nil(); + var phone = 'phone' in d && d.phone ? encode(d.phone) : nil(); var token = 'token' in d && d.token ? bin(d.token) : nil(); var type = 'type' in d && d.type ? atom(d.type) : nil(); var sms_code = 'sms_code' in d && d.sms_code ? bin(d.sms_code) : nil(); @@ -640,13 +1305,13 @@ function encAuth(d) { function lenAuth() { return 15; } function decAuth(d) { var r={}; r.tup = 'Auth'; - r.client_id = d && d.v[1] ? utf8_dec(d.v[1].v) : undefined; - r.dev_key = d && d.v[2] ? utf8_dec(d.v[2].v) : undefined; - r.user_id = d && d.v[3] ? utf8_dec(d.v[3].v) : undefined; - r.phone = d && d.v[4] ? utf8_dec(d.v[4].v) : undefined; - r.token = d && d.v[5] ? utf8_dec(d.v[5].v) : undefined; + r.client_id = d && d.v[1] ? utf8_arr(d.v[1].v) : undefined; + r.dev_key = d && d.v[2] ? utf8_arr(d.v[2].v) : undefined; + r.user_id = d && d.v[3] ? utf8_arr(d.v[3].v) : undefined; + r.phone = d && d.v[4] ? decode(d.v[4]) : undefined; + r.token = d && d.v[5] ? utf8_arr(d.v[5].v) : undefined; r.type = d && d.v[6] ? d.v[6].v : undefined; - r.sms_code = d && d.v[7] ? utf8_dec(d.v[7].v) : undefined; + r.sms_code = d && d.v[7] ? utf8_arr(d.v[7].v) : undefined; r.attempts = d && d.v[8] ? d.v[8].v : undefined; r.services = []; (d && d.v[9] && d.v[9].v) ? @@ -656,7 +1321,7 @@ function decAuth(d) { (d && d.v[10] && d.v[10].v) ? d.v[10].v.forEach(function(x){r.settings.push(decode(x))}) : r.settings = undefined; - r.push = d && d.v[11] ? utf8_dec(d.v[11].v) : undefined; + r.push = d && d.v[11] ? utf8_arr(d.v[11].v) : undefined; r.os = d && d.v[12] ? decode(d.v[12]) : undefined; r.created = d && d.v[13] ? d.v[13].v : undefined; r.last_online = d && d.v[14] ? d.v[14].v : undefined; @@ -664,7 +1329,7 @@ function decAuth(d) { function encRoster(d) { var tup = atom('Roster'); - var id = 'id' in d && d.id ? number(d.id) : nil(); + var id = 'id' in d && d.id ? encode(d.id) : nil(); var names = 'names' in d && d.names ? bin(d.names) : nil(); var surnames = 'surnames' in d && d.surnames ? bin(d.surnames) : nil(); var email = 'email' in d && d.email ? bin(d.email) : nil(); @@ -695,11 +1360,11 @@ function encRoster(d) { function lenRoster() { return 14; } function decRoster(d) { var r={}; r.tup = 'Roster'; - r.id = d && d.v[1] ? d.v[1].v : undefined; - r.names = d && d.v[2] ? utf8_dec(d.v[2].v) : undefined; - r.surnames = d && d.v[3] ? utf8_dec(d.v[3].v) : undefined; - r.email = d && d.v[4] ? utf8_dec(d.v[4].v) : undefined; - r.nick = d && d.v[5] ? utf8_dec(d.v[5].v) : undefined; + r.id = d && d.v[1] ? decode(d.v[1]) : undefined; + r.names = d && d.v[2] ? utf8_arr(d.v[2].v) : undefined; + r.surnames = d && d.v[3] ? utf8_arr(d.v[3].v) : undefined; + r.email = d && d.v[4] ? utf8_arr(d.v[4].v) : undefined; + r.nick = d && d.v[5] ? utf8_arr(d.v[5].v) : undefined; r.userlist = []; (d && d.v[6] && d.v[6].v) ? d.v[6].v.forEach(function(x){r.userlist.push(decode(x))}) : @@ -716,8 +1381,8 @@ function decRoster(d) { (d && d.v[9] && d.v[9].v) ? d.v[9].v.forEach(function(x){r.tags.push(decode(x))}) : r.tags = undefined; - r.phone = d && d.v[10] ? utf8_dec(d.v[10].v) : undefined; - r.avatar = d && d.v[11] ? utf8_dec(d.v[11].v) : undefined; + r.phone = d && d.v[10] ? utf8_arr(d.v[10].v) : undefined; + r.avatar = d && d.v[11] ? utf8_arr(d.v[11].v) : undefined; r.update = d && d.v[12] ? d.v[12].v : undefined; r.status = d && d.v[13] ? decode(d.v[13]) : undefined; return clean(r); } @@ -746,7 +1411,7 @@ function encProfile(d) { function lenProfile() { return 9; } function decProfile(d) { var r={}; r.tup = 'Profile'; - r.phone = d && d.v[1] ? utf8_dec(d.v[1].v) : undefined; + r.phone = d && d.v[1] ? utf8_arr(d.v[1].v) : undefined; r.services = []; (d && d.v[2] && d.v[2].v) ? d.v[2].v.forEach(function(x){r.services.push(decode(x))}) : @@ -765,6 +1430,19 @@ function decProfile(d) { r.status = d && d.v[8] ? decode(d.v[8]) : undefined; return clean(r); } +function encPresence(d) { + var tup = atom('Presence'); + var uid = 'uid' in d && d.uid ? bin(d.uid) : nil(); + var status = 'status' in d && d.status ? atom(d.status) : nil(); + return tuple(tup,uid,status); } + +function lenPresence() { return 3; } +function decPresence(d) { + var r={}; r.tup = 'Presence'; + r.uid = d && d.v[1] ? utf8_arr(d.v[1].v) : undefined; + r.status = d && d.v[2] ? decode(d.v[2]) : undefined; + return clean(r); } + function encFriend(d) { var tup = atom('Friend'); var phone_id = 'phone_id' in d && d.phone_id ? bin(d.phone_id) : nil(); @@ -779,8 +1457,8 @@ function encFriend(d) { function lenFriend() { return 5; } function decFriend(d) { var r={}; r.tup = 'Friend'; - r.phone_id = d && d.v[1] ? utf8_dec(d.v[1].v) : undefined; - r.friend_id = d && d.v[2] ? utf8_dec(d.v[2].v) : undefined; + r.phone_id = d && d.v[1] ? utf8_arr(d.v[1].v) : undefined; + r.friend_id = d && d.v[2] ? utf8_arr(d.v[2].v) : undefined; r.settings = []; (d && d.v[3] && d.v[3].v) ? d.v[3].v.forEach(function(x){r.settings.push(decode(x))}) : @@ -797,7 +1475,7 @@ function encact(d) { function lenact() { return 3; } function decact(d) { var r={}; r.tup = 'act'; - r.name = d && d.v[1] ? utf8_dec(d.v[1].v) : undefined; + r.name = d && d.v[1] ? utf8_arr(d.v[1].v) : undefined; r.data = d && d.v[2] ? decode(d.v[2]) : undefined; return clean(r); } @@ -869,7 +1547,7 @@ function encHistory(d) { function lenHistory() { return 7; } function decHistory(d) { var r={}; r.tup = 'History'; - r.roster_id = d && d.v[1] ? utf8_dec(d.v[1].v) : undefined; + r.roster_id = d && d.v[1] ? utf8_arr(d.v[1].v) : undefined; r.feed = d && d.v[2] ? decode(d.v[2]) : undefined; r.size = d && d.v[3] ? d.v[3].v : undefined; r.entity_id = d && d.v[4] ? d.v[4].v : undefined; @@ -931,7 +1609,7 @@ function encWhitelist(d) { function lenWhitelist() { return 3; } function decWhitelist(d) { var r={}; r.tup = 'Whitelist'; - r.phone = d && d.v[1] ? utf8_dec(d.v[1].v) : undefined; + r.phone = d && d.v[1] ? utf8_arr(d.v[1].v) : undefined; r.created = d && d.v[2] ? d.v[2].v : undefined; return clean(r); } @@ -954,7 +1632,7 @@ function encok(d) { function lenok() { return 2; } function decok(d) { var r={}; r.tup = 'ok'; - r.code = d && d.v[1] ? utf8_dec(d.v[1].v) : undefined; + r.code = d && d.v[1] ? utf8_arr(d.v[1].v) : undefined; return clean(r); } function encerror2(d) { @@ -996,6 +1674,19 @@ function decio(d) { r.data = d && d.v[2] ? decode(d.v[2]) : undefined; return clean(r); } +function encAck(d) { + var tup = atom('Ack'); + var table = 'table' in d && d.table ? atom(d.table) : nil(); + var id = 'id' in d && d.id ? bin(d.id) : nil(); + return tuple(tup,table,id); } + +function lenAck() { return 3; } +function decAck(d) { + var r={}; r.tup = 'Ack'; + r.table = d && d.v[1] ? d.v[1].v : undefined; + r.id = d && d.v[2] ? utf8_arr(d.v[2].v) : undefined; + return clean(r); } + function encerrors(d) { var tup = atom('errors'); var code = []; if ('code' in d && d.code) @@ -1035,11 +1726,11 @@ function decPushService(d) { (d && d.v[1] && d.v[1].v) ? d.v[1].v.forEach(function(x){r.recipients.push(decode(x))}) : r.recipients = undefined; - r.id = d && d.v[2] ? utf8_dec(d.v[2].v) : undefined; + r.id = d && d.v[2] ? utf8_arr(d.v[2].v) : undefined; r.ttl = d && d.v[3] ? d.v[3].v : undefined; - r.module = d && d.v[4] ? utf8_dec(d.v[4].v) : undefined; - r.priority = d && d.v[5] ? utf8_dec(d.v[5].v) : undefined; - r.payload = d && d.v[6] ? utf8_dec(d.v[6].v) : undefined; + r.module = d && d.v[4] ? utf8_arr(d.v[4].v) : undefined; + r.priority = d && d.v[5] ? utf8_arr(d.v[5].v) : undefined; + r.payload = d && d.v[6] ? utf8_arr(d.v[6].v) : undefined; return clean(r); } function encPublishService(d) { @@ -1052,8 +1743,8 @@ function encPublishService(d) { function lenPublishService() { return 4; } function decPublishService(d) { var r={}; r.tup = 'PublishService'; - r.message = d && d.v[1] ? utf8_dec(d.v[1].v) : undefined; - r.topic = d && d.v[2] ? utf8_dec(d.v[2].v) : undefined; + r.message = d && d.v[1] ? utf8_arr(d.v[1].v) : undefined; + r.topic = d && d.v[2] ? utf8_arr(d.v[2].v) : undefined; r.qos = d && d.v[3] ? d.v[3].v : undefined; return clean(r); } @@ -1066,7 +1757,255 @@ function encFakeNumbers(d) { function lenFakeNumbers() { return 3; } function decFakeNumbers(d) { var r={}; r.tup = 'FakeNumbers'; - r.phone = d && d.v[1] ? utf8_dec(d.v[1].v) : undefined; + r.phone = d && d.v[1] ? utf8_arr(d.v[1].v) : undefined; r.created = d && d.v[2] ? d.v[2].v : undefined; return clean(r); } +function enchandler(d) { + var tup = atom('handler'); + var name = 'name' in d && d.name ? atom(d.name) : nil(); + var module = 'module' in d && d.module ? atom(d.module) : nil(); + var class = 'class' in d && d.class ? encode(d.class) : nil(); + var group = 'group' in d && d.group ? atom(d.group) : nil(); + var config = 'config' in d && d.config ? encode(d.config) : nil(); + var state = 'state' in d && d.state ? encode(d.state) : nil(); + var seq = 'seq' in d && d.seq ? encode(d.seq) : nil(); + return tuple(tup,name,module,class,group,config,state,seq); } + +function lenhandler() { return 8; } +function dechandler(d) { + var r={}; r.tup = 'handler'; + r.name = d && d.v[1] ? d.v[1].v : undefined; + r.module = d && d.v[2] ? d.v[2].v : undefined; + r.class = d && d.v[3] ? decode(d.v[3]) : undefined; + r.group = d && d.v[4] ? d.v[4].v : undefined; + r.config = d && d.v[5] ? decode(d.v[5]) : undefined; + r.state = d && d.v[6] ? decode(d.v[6]) : undefined; + r.seq = d && d.v[7] ? decode(d.v[7]) : undefined; + return clean(r); } + +function encpi(d) { + var tup = atom('pi'); + var name = 'name' in d && d.name ? atom(d.name) : nil(); + var sup = 'sup' in d && d.sup ? atom(d.sup) : nil(); + var module = 'module' in d && d.module ? atom(d.module) : nil(); + var state = 'state' in d && d.state ? encode(d.state) : nil(); + return tuple(tup,name,sup,module,state); } + +function lenpi() { return 5; } +function decpi(d) { + var r={}; r.tup = 'pi'; + r.name = d && d.v[1] ? d.v[1].v : undefined; + r.sup = d && d.v[2] ? d.v[2].v : undefined; + r.module = d && d.v[3] ? d.v[3].v : undefined; + r.state = d && d.v[4] ? decode(d.v[4]) : undefined; + return clean(r); } + +function enccx(d) { + var tup = atom('cx'); + var handlers = []; if ('handlers' in d && d.handlers) + { d.handlers.forEach(function(x){ + handlers.push(encode(x))}); + handlers={t:108,v:handlers}; } else { handlers = nil() }; + var actions = []; if ('actions' in d && d.actions) + { d.actions.forEach(function(x){ + actions.push(encode(x))}); + actions={t:108,v:actions}; } else { actions = nil() }; + var req = 'req' in d && d.req ? encode(d.req) : nil(); + var module = 'module' in d && d.module ? atom(d.module) : nil(); + var lang = 'lang' in d && d.lang ? atom(d.lang) : nil(); + var path = 'path' in d && d.path ? bin(d.path) : nil(); + var session = 'session' in d && d.session ? bin(d.session) : nil(); + var formatter = 'formatter' in d && d.formatter ? atom(d.formatter) : nil(); + var params = []; if ('params' in d && d.params) + { d.params.forEach(function(x){ + params.push(encode(x))}); + params={t:108,v:params}; } else { params = nil() }; + var node = 'node' in d && d.node ? atom(d.node) : nil(); + var client_pid = 'client_pid' in d && d.client_pid ? encode(d.client_pid) : nil(); + var state = 'state' in d && d.state ? encode(d.state) : nil(); + var from = 'from' in d && d.from ? bin(d.from) : nil(); + var vsn = 'vsn' in d && d.vsn ? bin(d.vsn) : nil(); + return tuple(tup,handlers,actions,req,module,lang,path,session,formatter,params,node, + client_pid,state,from,vsn); } + +function lencx() { return 15; } +function deccx(d) { + var r={}; r.tup = 'cx'; + r.handlers = []; + (d && d.v[1] && d.v[1].v) ? + d.v[1].v.forEach(function(x){r.handlers.push(decode(x))}) : + r.handlers = undefined; + r.actions = []; + (d && d.v[2] && d.v[2].v) ? + d.v[2].v.forEach(function(x){r.actions.push(decode(x))}) : + r.actions = undefined; + r.req = d && d.v[3] ? decode(d.v[3]) : undefined; + r.module = d && d.v[4] ? d.v[4].v : undefined; + r.lang = d && d.v[5] ? d.v[5].v : undefined; + r.path = d && d.v[6] ? utf8_arr(d.v[6].v) : undefined; + r.session = d && d.v[7] ? utf8_arr(d.v[7].v) : undefined; + r.formatter = d && d.v[8] ? decode(d.v[8]) : undefined; + r.params = []; + (d && d.v[9] && d.v[9].v) ? + d.v[9].v.forEach(function(x){r.params.push(decode(x))}) : + r.params = undefined; + r.node = d && d.v[10] ? d.v[10].v : undefined; + r.client_pid = d && d.v[11] ? decode(d.v[11]) : undefined; + r.state = d && d.v[12] ? decode(d.v[12]) : undefined; + r.from = d && d.v[13] ? utf8_arr(d.v[13].v) : undefined; + r.vsn = d && d.v[14] ? utf8_arr(d.v[14].v) : undefined; + return clean(r); } + +function encbin(d) { + var tup = atom('bin'); + var data = 'data' in d && d.data ? encode(d.data) : nil(); + return tuple(tup,data); } + +function lenbin() { return 2; } +function decbin(d) { + var r={}; r.tup = 'bin'; + r.data = d && d.v[1] ? decode(d.v[1]) : undefined; + return clean(r); } + +function encclient(d) { + var tup = atom('client'); + var data = 'data' in d && d.data ? encode(d.data) : nil(); + return tuple(tup,data); } + +function lenclient() { return 2; } +function decclient(d) { + var r={}; r.tup = 'client'; + r.data = d && d.v[1] ? decode(d.v[1]) : undefined; + return clean(r); } + +function encserver(d) { + var tup = atom('server'); + var data = 'data' in d && d.data ? encode(d.data) : nil(); + return tuple(tup,data); } + +function lenserver() { return 2; } +function decserver(d) { + var r={}; r.tup = 'server'; + r.data = d && d.v[1] ? decode(d.v[1]) : undefined; + return clean(r); } + +function encinit(d) { + var tup = atom('init'); + var token = 'token' in d && d.token ? encode(d.token) : nil(); + return tuple(tup,token); } + +function leninit() { return 2; } +function decinit(d) { + var r={}; r.tup = 'init'; + r.token = d && d.v[1] ? decode(d.v[1]) : undefined; + return clean(r); } + +function encpickle(d) { + var tup = atom('pickle'); + var source = 'source' in d && d.source ? encode(d.source) : nil(); + var pickled = 'pickled' in d && d.pickled ? encode(d.pickled) : nil(); + var args = 'args' in d && d.args ? encode(d.args) : nil(); + return tuple(tup,source,pickled,args); } + +function lenpickle() { return 4; } +function decpickle(d) { + var r={}; r.tup = 'pickle'; + r.source = d && d.v[1] ? decode(d.v[1]) : undefined; + r.pickled = d && d.v[2] ? decode(d.v[2]) : undefined; + r.args = d && d.v[3] ? decode(d.v[3]) : undefined; + return clean(r); } + +function encflush(d) { + var tup = atom('flush'); + var data = 'data' in d && d.data ? encode(d.data) : nil(); + return tuple(tup,data); } + +function lenflush() { return 2; } +function decflush(d) { + var r={}; r.tup = 'flush'; + r.data = d && d.v[1] ? decode(d.v[1]) : undefined; + return clean(r); } + +function encdirect(d) { + var tup = atom('direct'); + var data = 'data' in d && d.data ? encode(d.data) : nil(); + return tuple(tup,data); } + +function lendirect() { return 2; } +function decdirect(d) { + var r={}; r.tup = 'direct'; + r.data = d && d.v[1] ? decode(d.v[1]) : undefined; + return clean(r); } + +function encev(d) { + var tup = atom('ev'); + var module = 'module' in d && d.module ? encode(d.module) : nil(); + var msg = 'msg' in d && d.msg ? encode(d.msg) : nil(); + var trigger = 'trigger' in d && d.trigger ? encode(d.trigger) : nil(); + var name = 'name' in d && d.name ? encode(d.name) : nil(); + return tuple(tup,module,msg,trigger,name); } + +function lenev() { return 5; } +function decev(d) { + var r={}; r.tup = 'ev'; + r.module = d && d.v[1] ? decode(d.v[1]) : undefined; + r.msg = d && d.v[2] ? decode(d.v[2]) : undefined; + r.trigger = d && d.v[3] ? decode(d.v[3]) : undefined; + r.name = d && d.v[4] ? decode(d.v[4]) : undefined; + return clean(r); } + +function encftp(d) { + var tup = atom('ftp'); + var id = 'id' in d && d.id ? encode(d.id) : nil(); + var sid = 'sid' in d && d.sid ? encode(d.sid) : nil(); + var filename = 'filename' in d && d.filename ? encode(d.filename) : nil(); + var meta = 'meta' in d && d.meta ? encode(d.meta) : nil(); + var size = 'size' in d && d.size ? encode(d.size) : nil(); + var offset = 'offset' in d && d.offset ? encode(d.offset) : nil(); + var block = 'block' in d && d.block ? encode(d.block) : nil(); + var data = 'data' in d && d.data ? encode(d.data) : nil(); + var status = 'status' in d && d.status ? encode(d.status) : nil(); + return tuple(tup,id,sid,filename,meta,size,offset,block,data,status); } + +function lenftp() { return 10; } +function decftp(d) { + var r={}; r.tup = 'ftp'; + r.id = d && d.v[1] ? decode(d.v[1]) : undefined; + r.sid = d && d.v[2] ? decode(d.v[2]) : undefined; + r.filename = d && d.v[3] ? decode(d.v[3]) : undefined; + r.meta = d && d.v[4] ? decode(d.v[4]) : undefined; + r.size = d && d.v[5] ? decode(d.v[5]) : undefined; + r.offset = d && d.v[6] ? decode(d.v[6]) : undefined; + r.block = d && d.v[7] ? decode(d.v[7]) : undefined; + r.data = d && d.v[8] ? decode(d.v[8]) : undefined; + r.status = d && d.v[9] ? decode(d.v[9]) : undefined; + return clean(r); } + +function encftpack(d) { + var tup = atom('ftpack'); + var id = 'id' in d && d.id ? encode(d.id) : nil(); + var sid = 'sid' in d && d.sid ? encode(d.sid) : nil(); + var filename = 'filename' in d && d.filename ? encode(d.filename) : nil(); + var meta = 'meta' in d && d.meta ? encode(d.meta) : nil(); + var size = 'size' in d && d.size ? encode(d.size) : nil(); + var offset = 'offset' in d && d.offset ? encode(d.offset) : nil(); + var block = 'block' in d && d.block ? encode(d.block) : nil(); + var data = 'data' in d && d.data ? encode(d.data) : nil(); + var status = 'status' in d && d.status ? encode(d.status) : nil(); + return tuple(tup,id,sid,filename,meta,size,offset,block,data,status); } + +function lenftpack() { return 10; } +function decftpack(d) { + var r={}; r.tup = 'ftpack'; + r.id = d && d.v[1] ? decode(d.v[1]) : undefined; + r.sid = d && d.v[2] ? decode(d.v[2]) : undefined; + r.filename = d && d.v[3] ? decode(d.v[3]) : undefined; + r.meta = d && d.v[4] ? decode(d.v[4]) : undefined; + r.size = d && d.v[5] ? decode(d.v[5]) : undefined; + r.offset = d && d.v[6] ? decode(d.v[6]) : undefined; + r.block = d && d.v[7] ? decode(d.v[7]) : undefined; + r.data = d && d.v[8] ? decode(d.v[8]) : undefined; + r.status = d && d.v[9] ? decode(d.v[9]) : undefined; + return clean(r); } + diff --git a/apps/roster/priv/migrations/20180405173126_unsub_auth.erl b/apps/roster/priv/migrations/20180405173126_unsub_auth.erl index f776a7c38bdf175d63c16ff0cda037e3fb04efb9..cc98a71434ba618b965c8df94968bb839433799d 100644 --- a/apps/roster/priv/migrations/20180405173126_unsub_auth.erl +++ b/apps/roster/priv/migrations/20180405173126_unsub_auth.erl @@ -8,7 +8,7 @@ }). up() -> - [n2o:unsubscribe(ClientId, Topic) + [n2o_vnode:unsubscribe(ClientId, Topic) ||#mqtt_topic{topic = <<"auth/", ClientId/binary>> = Topic} <-kvs:all(mqtt_topic)], ok. diff --git a/apps/roster/priv/migrations/20181012141608_link_desc_re.erl b/apps/roster/priv/migrations/20181012141608_link_desc_re.erl new file mode 100644 index 0000000000000000000000000000000000000000..d12b1164f61c65e66ca0eacc82328eb4a8ff7cbc --- /dev/null +++ b/apps/roster/priv/migrations/20181012141608_link_desc_re.erl @@ -0,0 +1,18 @@ +-module('20181012141608_link_desc_re'). +-behavior(db_migration). +-export([up/0, down/0]). + +-include_lib("../../include/roster.hrl"). + +upd(#'Message'{files = Descs} = Msg) -> + Msg#'Message'{files = lists:flatten([roster:parse_desc(Desc)|| #'Desc'{} = Desc<-Descs])}. + +up() -> + [case upd(M) of + #'Message'{files = Descs2} when length(Descs) == length(Descs2) -> []; + Msg -> kvs:put(Msg) + end || M = #'Message'{files = Descs, type = Type} <- kvs:all('Message'), Type /= [sys]], + ok. + +down() -> + ok. diff --git a/apps/roster/src/api/prometheus_api.erl b/apps/roster/src/api/prometheus_api.erl index 49f7b7231fd1dbd565ed8099b798884e1df5930e..e68a227674e40a68a8201d7369174af99aeffffc 100644 --- a/apps/roster/src/api/prometheus_api.erl +++ b/apps/roster/src/api/prometheus_api.erl @@ -2,8 +2,8 @@ %% Prometheus metrics declaration module -module(prometheus_api). --include_lib("roster/static/prometheus_text.hrl"). --include_lib("roster/static/prometheus_var.hrl"). +-include_lib("roster/include/static/prometheus_text.hrl"). +-include_lib("roster/include/static/prometheus_var.hrl"). -export([init/0]). @@ -19,4 +19,8 @@ init() -> prometheus_gauge:declare([{name, Name}, {help, Desc}, {labels, Label}]), roster:info(?MODULE, "InitMetric:~p", [Name]) end || {Name, Desc, Label} <- GaugeMetrics], + prometheus_histogram:declare([{name, roster_msg_latency}, + {labels, [mile]}, + {buckets, [50, 100, 200, 400, 1000, 2000, 5000, 10000, 20000]}, + {help, "Message Latency"}]), ok. diff --git a/apps/roster/src/api/push/ios.erl b/apps/roster/src/api/push/ios.erl index 71e9d8b0934327de91fe8bce127797e3bb078d57..ef79bee0925f35325438b7be079bb94a910e3d5b 100644 --- a/apps/roster/src/api/push/ios.erl +++ b/apps/roster/src/api/push/ios.erl @@ -1,6 +1,6 @@ -module(ios). -include("roster.hrl"). --include_lib("roster/static/push_notification_var.hrl"). +-include_lib("roster/include/static/push_notification_var.hrl"). -export([description/0, notify/5, test_push_notification/0]). diff --git a/apps/roster/src/api/stickers_api.erl b/apps/roster/src/api/stickers_api.erl index 1eeef4f44afe9c8ff62f29ed9579ca7c549ace89..f7e592a01f7e15ea64468ba039728cc5709c9328 100644 --- a/apps/roster/src/api/stickers_api.erl +++ b/apps/roster/src/api/stickers_api.erl @@ -34,7 +34,7 @@ description() -> "Fill default sticker pack for Nynja App". -define(KEY_CREATED_DATE, <<"CREATED">>). prepare_sticker_desc_feature(DescId, Key, Value) -> - #'Feature'{group = ?GROUP, key = Key, id = iolist_to_binary([DescId, "_", string:lowercase(Key), "_", integer_to_binary(kvs:next_id('Feature', 1))]), value = Value}. + #'Feature'{group = ?GROUP, key = Key, id = iolist_to_binary([DescId, "_", string:to_lower(nitro:to_list(Key)), "_", integer_to_binary(kvs:next_id('Feature', 1))]), value = Value}. prepare_sticker_desc(LinkToFile, EmojiList, KeywordList, PackId) when is_integer(PackId) -> prepare_sticker_desc(LinkToFile, EmojiList, KeywordList, integer_to_binary(PackId)); diff --git a/apps/roster/src/micro.erl b/apps/roster/src/micro.erl new file mode 100644 index 0000000000000000000000000000000000000000..895ccefcc8d5de0b220bbc483492b46a0072f26d --- /dev/null +++ b/apps/roster/src/micro.erl @@ -0,0 +1,67 @@ +-module(micro). +-compile(export_all). +-include_lib("kvs/include/metainfo.hrl"). +-include("roster.hrl"). +-include("micro.hrl"). + +metainfo() -> #schema{name = kvs, tables = tables()}. +tables() -> [ + %% one2one table for micro account and phone_id + #table{name = 'LinkRoster', fields = record_info(fields, 'LinkRoster'), keys = [phone_id]}, + %% one2one table for micro profile and phone + #table{name = 'LinkProfile', fields = record_info(fields, 'LinkProfile'), keys = [phone]} +]. + +is_phone_id(PhoneId) -> + case binary:split(PhoneId, <<"_">>) of + [Phone, Id] -> true; _ -> false end. +is_link_acc(Acc) -> uuid:is_valid(Acc). + +uuid_to_id(Table, <<_:128>> = Acc) -> + case uuid:is_valid(Acc) of + true -> case kvs:get(Table, Acc) of + {ok, {Table, _, PhoneId}} -> PhoneId; + _ -> [] + end; + _ -> [] + end; +uuid_to_id(Table, Acc) -> + uuid_to_id(Table, uuid:to_binary(binary_to_list(Acc))). + +norm_uuid(<<_:128>> = Uuid) -> list_to_binary(uuid:to_string(Uuid)); +norm_uuid(Uuid) -> Uuid. + +uuid_to_bin(<<_:128>> = Uuid) -> Uuid; +uuid_to_bin(Uuid) -> uuid:to_binary(binary_to_list(Uuid)). + +to_uuid(phone, Data) -> uuid:uuid3(<<"phone">>, Data); +to_uuid(phone_id, Data) -> uuid:uuid3(<<"phone_id">>, Data). + +purge_user(Phone) -> + case kvs:get('Profile', Phone) of + {ok, #'Profile'{rosters = RosterIds}} -> + [case kvs:index('LinkRoster', phone_id, roster:phone_id(Phone, RosterId)) of + [#'LinkRoster'{id = Id}] -> kvs:delete('LinkRoster', Id); + [] -> ok + end || RosterId<-RosterIds], + roster:purge_user(Phone); + {error, _ } -> ok + end. + +link_user(#'Profile'{phone = Phone, rosters = RosterIds}) -> + kvs:put(#'LinkProfile'{id = to_uuid(phone, Phone), phone = Phone}), + [kvs:put(#'LinkRoster'{id = to_uuid(phone_id, roster:phone_id(Phone, RosterId)), + phone_id = roster:phone_id(Phone, RosterId)}) + || RosterId<-RosterIds], ok; +link_user(Phone) -> + case kvs:get('Profile', Phone) of + {ok, #'Profile'{} = P} -> link_user(P); + {error, _ } -> ok + end. + +link_users() -> + [link_user(Profile) || Profile <- kvs:all('Profile')], ok. + +validate_token(_Token) -> {ok, <<"dummy-account-uuid">>}. %%TODO temporary +%%validate_token(<<"expired">>) -> expired; +%%validate_token(_) -> error. diff --git a/apps/roster/src/model/CallBubbleJSONRest.erl b/apps/roster/src/model/CallBubbleJSONRest.erl new file mode 100644 index 0000000000000000000000000000000000000000..1670bb5ea86ecf81e4923790d8932ca29e4083a9 --- /dev/null +++ b/apps/roster/src/model/CallBubbleJSONRest.erl @@ -0,0 +1,5 @@ +-module('CallBubbleJSONRest'). +-compile({parse_transform, rest}). +-include("rest_static.hrl"). +-rest_record('CallBubbleJSON'). +-compile(export_all). \ No newline at end of file diff --git a/apps/roster/src/processes/job.erl b/apps/roster/src/processes/job.erl index 51c0367635f89e74a0e19616155f08e60846734d..c2c6497cea42bd44ce97a9f0f34954a1e2518366 100644 --- a/apps/roster/src/processes/job.erl +++ b/apps/roster/src/processes/job.erl @@ -73,37 +73,34 @@ worker(#process{id=Id}=P) -> case kvs:get(feed,process) of {ok,Feed} when Feed#feed.top =:= Id -> case bpe:hist(Id) of - [H|_] ->% kvs:info(?MODULE,"Worker Start: ~p~n",[Id]), - worker_do(calendar:time_difference(H#hist.time,calendar:local_time()),P); + [H|_] ->%kvs:info(?MODULE,"Worker Start: ~p~n",[Id]), + worker_do(calendar:time_difference(H#hist.time,calendar:local_time()),P); __ -> skip end; __ -> skip end . worker_do({Days,Time},P) when Days >= 14 -> skip; worker_do({Days,Time},#process{id=Id}=P) when P#process.task =:= 'Action' -> - %n2o_async:pid(system,roster_bpe) ! {restart, P}, +% 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}=P) when P#process.task =:= 'Stop' -> +worker_do({Days,Time},#process{id=Id, options = Ops}=P) when P#process.task =:= 'Stop' -> bpe:start(P, []), - catch n2o_async:pid(system,roster_bpe) ! {restart, P}, % bpe:complite(Id), io:format("BPE Start: ~p~n",[Id]); worker_do({Days,Time},#process{id=Id}=P) when P#process.task =:= 'Init' -> bpe:start(P, []), -% catch n2o_async:pid(system,roster_bpe) ! {restart, 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' -> bpe:start(P, []), - kvs:info(?MODULE,"BPE Start: ~p~n",[Id]), - catch n2o_async:pid(system,roster_bpe) ! {restart, P}; + kvs:info(?MODULE,"BPE Start: ~p~n",[Id]); % bpe:complite(Id); -worker_do({Days,Time},P) -> skip. +worker_do({Days,Time},P) ->kvs:info(?MODULE,"BPE Start: ~p~n",[P]). %skip. next(Fun,R,Current,Acc) -> diff --git a/apps/roster/src/processes/job_process.erl b/apps/roster/src/processes/job_process.erl index 44464388bfdecaf15f57c263f24c65d256086826..4662dec78862b63eedcea13eb04632a57630f02e 100644 --- a/apps/roster/src/processes/job_process.erl +++ b/apps/roster/src/processes/job_process.erl @@ -75,7 +75,7 @@ update_event(Proc,Task,T)-> Events=lists:keystore(Task,#timeoutEvent.name,bpe:events(Proc),#timeoutEvent{name=Task, timeout=T, module=job }), NewP=Proc#process{events = Events}, kvs:put(NewP), NewP - . +. update_opt(#process{options = Opts}=P,Opt)-> %Proc=bpe:load(Id), diff --git a/apps/roster/src/protocol/channel/roster_channel.erl b/apps/roster/src/protocol/channel/roster_channel.erl deleted file mode 100644 index 4fa3e7a8ed672a92c3cf498e05c9aa3107882af9..0000000000000000000000000000000000000000 --- a/apps/roster/src/protocol/channel/roster_channel.erl +++ /dev/null @@ -1,135 +0,0 @@ --module(roster_channel). --include("roster.hrl"). --include_lib("roster/static/room_channel_text.hrl"). --include_lib("roster/static/room_channel_var.hrl"). --include_lib("roster/static/main_text.hrl"). --include_lib("roster/static/main_var.hrl"). --include_lib("n2o/include/n2o.hrl"). --compile(export_all). - -info(#'Room'{status = create, id = ChannelId, name = Name, admins = [], links = [#'Link'{name = Link}]} = RequestData, - Req, #cx{params = ClientId, client_pid = C} = State) when ChannelId /= [] andalso Name /= [] andalso Link /= [] -> - roster:info(?MODULE, "~p:Channel/Create:~p", [ClientId, RequestData]), - R = case roster_channel_helper:get_channel_by_id(ChannelId) of - {ok, _} -> roster_channel_helper:error_response(?ERROR_CREATE_EXISTING_CHANNEL, RequestData); - {error, _} -> - case roster_channel_helper:validate_link_format(Link) of - {error, LinkValidationError} -> roster_channel_helper:error_response(LinkValidationError, RequestData); - {ok, _} -> - case roster_channel_helper:check_link_availability(Link) of - {error, LinkAvailabilityError} -> roster_channel_helper:error_response(LinkAvailabilityError, RequestData); - {ok, _} -> - kvs_stream:save(#writer{id = Feed = #muc{name = ChannelId}}), - %% create channel object - NewChannel = RequestData#'Room'{members = [], admins = [], created = roster:now_msec(), - links = [#'Link'{id = roster_channel_helper:generate_link_id(ChannelId), name = Link, created = roster:now_msec()}]}, - %% create owner object into channel - OwnerPhoneId = roster:phone_id(ClientId), - OwnerMemberPrepatch = #'Member'{id = [], feed_id = Feed, status = owner, phone_id = OwnerPhoneId}, - {#'Member'{reader = OwnerReader} = OwnerMember, _, _} = roster:add_member(NewChannel, OwnerMemberPrepatch), - %% add system msg about room creation - Msg = roster:add_message(#'Message'{type = ?SYS_MSG_TYPE, feed_id = Feed, from = OwnerPhoneId, - to = ChannelId, files = [#'Desc'{payload = ?SYS_MSG_CREATE_CHANNEL}]}, OwnerReader), - %% update subscribers - NewChannelWithFeature = roster_channel_helper:set_count_features(NewChannel), - NewChannelWithLastMsg = NewChannelWithFeature#'Room'{last_msg = Msg#'Message'.id, update = roster:now_msec()}, - kvs:put(NewChannelWithLastMsg), - roster_channel_helper:add_link_index(Link, ChannelId), - roster_channel_helper:update_members_roster(NewChannelWithLastMsg), - %% send room object - roster_channel_helper:send_channel(C, NewChannelWithLastMsg#'Room'{members = [OwnerMember]}), - <<>> - end - end - end, - roster:info(?MODULE, "Channel/create.Response:~p", [R]), - {reply, {bert, R}, Req, State}; - -info(#'Room'{status = add, id = ChannelId, members = Members} = RequestData, Req, - #cx{params = ClientId, client_pid = C} = State) -> - roster:info(?MODULE, "~p:Channel/addSubscribers:~p", [ClientId, RequestData]), - R = case roster_channel_helper:get_channel_by_id(ChannelId) of - {error, GetChannelError} -> roster_channel_helper:error_response(GetChannelError, RequestData); - {ok, Channel} -> - case roster_channel_helper:check_user_permissions(ChannelId, ClientId, ?FKC_ADD_SUBSCR) of - {error, MemberPermissionError} -> roster_channel_helper:error_response(MemberPermissionError, RequestData); - {ok, _} -> -%% add members to channel - roster_channel_helper:add_members(Channel, Members), -%% update subscribers count - ChannelWithSubscribers = roster_channel_helper:set_count_features(Channel), - UpdatedChannel = ChannelWithSubscribers#'Room'{update = roster:now_msec(), status = add}, - kvs:put(UpdatedChannel), - roster_channel_helper:update_members_roster(UpdatedChannel), - roster_channel_helper:send_channel(C, UpdatedChannel), - <<>> - end - end, - roster:info(?MODULE, "Channel/addSubscribers.Response:~p", [R]), - {reply, {bert, R}, Req, State}; - -%% TODO add check for banned members -info(#'Room'{status = get, id = ChannelId} = RequestData, Req, #cx{params = ClientId} = State) -> - roster:info(?MODULE, "~p:Channel/get:~p", [ClientId, ChannelId]), - R = case roster_channel_helper:get_channel_by_id(ChannelId) of - {error, GetChannelError} -> roster_channel_helper:error_response(GetChannelError, RequestData); - {ok, Channel} -> - ActedUserPhoneId = roster:phone_id(ClientId), - roster_channel_helper:channel_response_object(Channel#'Room'{status = get, - members = case roster:muc_member(ActedUserPhoneId, ChannelId) of [] -> []; #'Member'{} = ActedMember -> [ActedMember] end}) - end, - roster:info(?MODULE, "Channel/get.Response:~p", [R]), - {reply, {bert, R}, Req, State}; - -info(#'Room'{status = patch, id = ChannelId, name = NewName, data = NewData} = RequestData, Req, - #cx{params = ClientId, client_pid = C} = State) -> - roster:info(?MODULE, "~p:Channel/patch:~p", [ClientId, RequestData]), - R = case roster_channel_helper:get_channel_by_id(ChannelId) of - {error, GetChannelError} -> roster_channel_helper:error_response(GetChannelError, RequestData); - {ok, #'Room'{name = StoredName, data = StoredData} = ExistingChannel} -> - case roster_channel_helper:check_user_permissions(ChannelId, ClientId, ?FKC_INFO_MNGT) of - {error, MemberPermissionError} -> roster_channel_helper:error_response(MemberPermissionError, RequestData); - {ok, #'Member'{phone_id = ActedMemberPhoneId, reader = ActedMemberReader}} -> -%% update channel -%% TODO Optimize it, use common with room method - PatchedChannel = roster:patch_record(?CHANNEL_UPDATE_EXCLUDED_FIELDS, ExistingChannel, RequestData#'Room'{update = roster:now_msec()}, record_info(fields, 'Room')), -%% check is new avatar set - PatchedChannelAvatar = - case NewData of - [#'Desc'{id = AvatarId}] -> - case lists:keyfind(AvatarId, #'Desc'.id, StoredData) of - false -> -%% add system message about new avatar -%% TODO pay attention to this sys msg, looks like it has some problems - NewPhotoMsg = roster:add_message(#'Message'{type = ?SYS_MSG_TYPE, feed_id = #muc{name = ChannelId}, - from = ActedMemberPhoneId, to = ChannelId, - files = [#'Desc'{payload = iolist_to_binary([?SYS_MSG_UPDATE_GROUP_AVATAR])}, NewData]}, ActedMemberReader), - PatchedChannel#'Room'{last_msg = NewPhotoMsg#'Message'.id, update = roster:now_msec()}; - _ -> PatchedChannel - end; - _ -> skip - end, -%% check is new channel name set - PatchedChannelAvatarName = - case NewName of - Name when Name /= [] andalso Name /= StoredName andalso Name /= <<>> -> -%% add system message about new name - NewNameMsg = roster:add_message(#'Message'{type = ?SYS_MSG_TYPE, feed_id = #muc{name = ChannelId}, -%% TODO replace msg payload in common for channel and group action - from = ActedMemberPhoneId, to = ChannelId, files = [#'Desc'{payload = iolist_to_binary([?SYS_MSG_UPDATE_CHANNEL_NAME, " \"", NewName, "\""])}]}, ActedMemberReader), - PatchedChannelAvatar#'Room'{last_msg = NewNameMsg#'Message'.id, update = roster:now_msec()}; - _ -> PatchedChannelAvatar - end, - kvs:put(PatchedChannelAvatarName), -%% update room in members' rosters - roster_channel_helper:update_members_roster(PatchedChannelAvatarName), - roster_channel_helper:send_channel(C, PatchedChannelAvatarName), - <<>> - end - end, - roster:info(?MODULE, "Channel/patch.Response:~p", [R]), - {reply, {bert, R}, Req, State}; - -info(#'Room'{} = RequestData, Req, #cx{params = ClientId} = State) -> - roster:info(?MODULE, "~p:Channel/unknown:~p", [ClientId, RequestData]), - {reply, {bert, roster_channel_helper:error_response_400(RequestData)}, Req, State}. \ No newline at end of file diff --git a/apps/roster/src/protocol/channel/roster_channel_helper.erl b/apps/roster/src/protocol/channel/roster_channel_helper.erl deleted file mode 100644 index fbe513e1fee5926ffc59870b1c95482a1920dd10..0000000000000000000000000000000000000000 --- a/apps/roster/src/protocol/channel/roster_channel_helper.erl +++ /dev/null @@ -1,277 +0,0 @@ --module(roster_channel_helper). --include("roster.hrl"). --include_lib("roster/static/room_channel_text.hrl"). --include_lib("roster/static/room_channel_var.hrl"). --include_lib("roster/static/main_text.hrl"). --compile(export_all). - -%% --------------------------------------------------------------------------------------------------------------------- -%% Errors Management -%% --------------------------------------------------------------------------------------------------------------------- - -%% TODO replace error_response section to another helper in future -error_response(ListOfCodes, Model) when not is_list(ListOfCodes) -> - error_response([ListOfCodes], Model); -error_response(ListOfCodes, Model) -> - #errors{code = ListOfCodes, data = Model}. - -error_response_403(Model) -> -%% permissions denied error response - error_response(?ERROR_PERMISSION_DENIED, Model). - -error_response_400(Model) -> -%% invalid data error response - error_response(?ERROR_INVALID_DATA, Model). - -%% --------------------------------------------------------------------------------------------------------------------- -%% Channel Management -%% --------------------------------------------------------------------------------------------------------------------- - -is_channel(FeedId) -> - case FeedId of #muc{name = RoomId} -> case kvs:get('Room', RoomId) of {ok, #'Room'{type = channel}} -> true; _ -> false end; _ -> false end. - -get_channel_by_id(ChannelId) -> -%% Check is channel exists -%% Params => ChannelId :: binary() -%% Return => Succes -> {ok, ChannelObject}; Error -> {error, ChannelNotFoundMsg} - case kvs:get('Room', ChannelId) of - {error, _} -> {error, ?ERROR_CHANNEL_NOT_FOUND}; - {ok, Channel} -> {ok, Channel} - end. - -get_channel_by_link(Link) when is_list(Link) -> - get_channel_by_link(list_to_binary(Link)); -get_channel_by_link(Link) -> - case kvs:get('Index', {?LINK_INDEX_KEYWORD, string:lowercase(Link)}) of - {ok, #'Index'{roster = [RoomId]}} -> - case kvs:get('Room', RoomId) of - {ok, Room} -> channel_response_object(Room); - {error, _} -> [] - end; - _ -> [] - end. - -get_room_avatar(#'Room'{data = Descs}) -> - case lists:keyfind(?ROOM_AVATAR_MIME, #'Desc'.mime, Descs) of #'Desc'{payload = LinkToAvatar} -> LinkToAvatar; _ -> [] end. - -get_last_msg(MsgId) -> - case kvs:get('Message', MsgId) of - {ok, Msg} -> Msg; - {error, _} -> [] - end. - -channel_response_object(#'Room'{id = RoomId, links = Links, last_msg = LastMsgId} = Channel) -> -%% add extra-processing for links and last msg - Channel#'Room'{links = [Link#'Link'{room_id = RoomId} || Link <- Links], last_msg = get_last_msg(LastMsgId)}. - -%% TODO check permissions function: check is member exists / is member owner / is member admin with OK permittions to make changes - -%% Broadcast functions - -send_channel(ClientPid, ChannelId, Msg) -> - roster:send_room(ClientPid, ChannelId, Msg). - -send_channel(ClientPid, Channel) -> - roster:send_room(ClientPid, channel_response_object(Channel)). - -send_admin(ClientPid, #'Room'{id = ChannelId} = Channel) -> -%% notify all channel admins and owner - [roster:send_ses(ClientPid, MemberPhoneId, channel_response_object(Channel)) || #'Member'{phone_id = MemberPhoneId} <- roster:members(#muc{name = ChannelId}, admin)]. - -%% --------------------------------------------------------------------------------------------------------------------- -%% Member Management -%% --------------------------------------------------------------------------------------------------------------------- - -member_get_feed(MemberId) -> -%% get member feed by member id -%% return member feed - case kvs:get('Member', MemberId) of - {ok, #'Member'{feed_id = FeedId}} -> FeedId; - _ -> [] - end. - -check_user_permissions(ChannelId, ClientId) -> - check_user_permissions(ChannelId, ClientId, []). -check_user_permissions(ChannelId, ClientId, PermissionKey) -> -%% Check acted user permissions -%% Params => ChannelId :: binary(); ClientId :: binary(); PermissionKey :: binary() -%% Return => Success -> {ok, MemberObject}; Error -> {error, PermissionsDeniedMsg} -%% get Acted user member in this Channel - ResponseError = {error, ?ERROR_PERMISSION_DENIED}, - case roster:muc_member(ClientId, ChannelId) of - [] -> ResponseError; - #'Member'{status = ActedMemberStatus, settings = PermList} = ActedMember -> - ResponseSuccess = {ok, ActedMember}, -%% check Acted user member status - case ActedMemberStatus of - owner -> ResponseSuccess; - admin -> - case PermissionKey of - [] -> ResponseSuccess; - _ -> -%% check admin permission list - case lists:keyfind(PermissionKey, #'Feature'.key, PermList) of - #'Feature'{value = true} -> ResponseSuccess; - _ -> ResponseError - end - end; - _ -> ResponseError - end - end. - -add_members(#'Room'{id = ChannelId, readers = ChannelReaders} = Channel, Members) -> -%% Proceed Members to be added - [begin -%% check is this member already added - case roster:muc_member(MemberPhoneId, ChannelId) of - [] -> -%% add new member - roster:add_member(Channel, #'Member'{id = [], feed_id = #muc{name = ChannelId}, status = member, - phone_id = MemberPhoneId}); - #'Member'{status = MemberStatus} = ExistingMember -> -%% check was member removed. if true - change his status from removed to member - case MemberStatus of - removed -> - PrePatchedMember = roster:patch_member(ExistingMember#'Member'{status = member}, ExistingMember), - {PatchedMember, _, _} = roster:add_member(Channel, PrePatchedMember, {no_muc_message, ChannelReaders}), - kvs:put(PatchedMember); - _ -> skip - end - end - end || #'Member'{phone_id = MemberPhoneId} <- Members]. - -count_members(ChannelId, Status) -> -%% count channel members filtered by statuses -%% active: member, owner, admin -%% admin: member, owner - length(roster:members(#muc{name = ChannelId}, Status)). - -update_members_roster(#'Room'{id = ChannelId} = Channel) -> - [roster:update_rooms(MemberPhoneId, Channel) || #'Member'{phone_id = MemberPhoneId} <- roster:members(#muc{name = ChannelId})]. - -%% --------------------------------------------------------------------------------------------------------------------- -%% Feature Management -%% --------------------------------------------------------------------------------------------------------------------- - -update_feature(FeatureKey, FeatureValue, ExistingFeatures) -> - (case lists:keyfind(FeatureKey, #'Feature'.key, ExistingFeatures) of - false -> #'Feature'{id = iolist_to_binary([<<"srv_">>, i2b(kvs:next_id('Desc', 1)), "_", i2b(roster:now_msec())]), - key = FeatureKey, group = ?FGC_INFO}; - ExistingFeature -> ExistingFeature - end)#'Feature'{value = FeatureValue}. - -set_count_features(#'Room'{settings = ExistingFeatures, id = ChannelId} = Channel) -> -%% NOTE! Should update Channel Subscribers Count on: Room/add, Room/remove, Room/join, Room/leave -%% NOTE! Active member means that Member.status is not *removed* - SubscribersCountFeature = update_feature(?FKC_SUBSCRIBERS_COUNT, i2b(count_members(ChannelId, active)), ExistingFeatures), -%% NOTE! Admin member means that Member.status is *admin* or *owner* - AdminsCountFeature = update_feature(?FKC_ADMINS_COUNT, i2b(count_members(ChannelId, admin)), ExistingFeatures), - SettingsUpdSubscribersCountFeature = lists:keystore(?FKC_SUBSCRIBERS_COUNT, #'Feature'.key, ExistingFeatures, SubscribersCountFeature), - SettingsUpdAdminsCountFeature = lists:keystore(?FKC_ADMINS_COUNT, #'Feature'.key, SettingsUpdSubscribersCountFeature, AdminsCountFeature), - Channel#'Room'{settings = SettingsUpdAdminsCountFeature}. - -%% --------------------------------------------------------------------------------------------------------------------- -%% Link Management -%% --------------------------------------------------------------------------------------------------------------------- - -generate_link_id(ChannelId) -> - iolist_to_binary([ChannelId, <<"_">>, integer_to_list(kvs:next_id('Link', 1))]). - -generate_link() -> -%% 1. Create random string based on timestamp -%% 2. Delete not allowed symbols - list_to_binary(lists:filter(fun(Char) -> lists:member(Char, binary_to_list(?LINK_ALLOWED_CHARS)) end, - binary_to_list(base64:encode(crypto:hmac(sha256, i2b(roster:now_msec()), i2b(roster:now_msec())))))). - -validate_link_format(Link) -> - case regexp_api:validate_string(Link, lists:flatten(io_lib:format(?LINK_REGEXP, [?LINK_LEN_MIN - 1, ?LINK_LEN_MAX - 1]))) of - {ok, _} -> {ok, Link}; - {error, _} -> {error, ?LINK_INVALID_FORMAT} - end. - -check_link_availability(Link) -> - check_link_availability(Link, []). -check_link_availability(Link, ChannelId) -> - ResponseError = {error, ?LINK_NOT_AVAILABLE}, - ResponseSuccess = {ok, []}, - case kvs:get('Index', {?LINK_INDEX_KEYWORD, string:lowercase(Link)}) of - {error, _} -> ResponseSuccess; - {ok, #'Index'{roster = EntityList}} -> - case lists:member(ChannelId, EntityList) of - false -> ResponseError; - _ -> ResponseSuccess - end - end. - -add_link_index(Link, RoomId) -> - kvs:put(#'Index'{id = {?LINK_INDEX_KEYWORD, string:lowercase(Link)}, roster = [RoomId]}). - -delete_link_index(Link) -> - kvs:delete('Index', {?LINK_INDEX_KEYWORD, string:lowercase(Link)}). - -%% lists:keyfind(LinkId, #'Link'.id, StoredLinks) -get_link_by_id(LinkId, LinksList) -> -%% Get is Link with requested id exists in Stored Links List -%% Params => LinkId :: binary(), LinksList :: list of #'Link'{} objects -%% Return => Success -> {ok, #'Link'{}}; Error -> {error, LinkNotFoundError} - case lists:keyfind(LinkId, #'Link'.id, LinksList) of false -> {error, ?ERROR_LINK_NOT_FOUND}; #'Link'{} = StoredLink -> {ok, StoredLink} end. - -get_link_count(#'Room'{links = LinkList}) -> -%% Get links count in Channel -%% Params => Channel :: ErlangRecord -%% Return => Success -> LinksCountInteger - length(LinkList). - -check_link_add_availability(Channel) -> -%% Check existing links count in Channel -%% Params => Channel :: ErlangRecord -%% Return => Success -> {ok, []}; Error -> {error, MaxNumberOfLinksReachedMsg} - case get_link_count(Channel) of LinksCount when LinksCount >= ?MAX_LINKS_NUM -> {error, ?MAX_LINKS_NUM_REACHED}; _ -> {ok, []} end. - -%% --------------------------------------------------------------------------------------------------------------------- -%% Non-grouped helpers -%% --------------------------------------------------------------------------------------------------------------------- - -i2b(X) -> integer_to_binary(X). - -paginator(List, Limit, Offset) -> -%% protect from empty list slicing - case length(List) of - 0 -> []; - _ -> -%% protect from invalid Offset value - ValidatedOffset = case Offset of - O when O > length(List) -> length(List); - O when O == []; O == 0 -> 1; - _ -> Offset end, -%% protect from invalid Limit value - ValidatedLimit = case Limit of - L when L < 0 -> 1; - _ -> Limit end, - lists:sublist(List, ValidatedOffset, ValidatedLimit) - end. - -startswith(String, Head) when is_binary(String) -> - startswith(binary_to_list(String), Head); -startswith(String, Head) when is_binary(Head) -> - startswith(String, binary_to_list(Head)); -startswith(String, Head) -> - string:str(string:lowercase(String), string:lowercase(Head)) > 0. - -%% TODO - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -%% TODO delete debug helpers after development -%% TODO - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -delete_test_channel() -> - TestChannelId = <<"channel_1">>, -%% delete members - [begin - kvs:delete('Member', MemId) - end || #'Member'{id = MemId} <- roster:members(#muc{name = TestChannelId})], -%% delete messages - [begin - kvs:delete('Message', MsgId) - end || #'Message'{id = MsgId} <- kvs:index('Message', to, TestChannelId)], -%% delete channel - kvs:delete('Room', TestChannelId), - ok. \ No newline at end of file diff --git a/apps/roster/src/protocol/channel/roster_channel_history.erl b/apps/roster/src/protocol/channel/roster_channel_history.erl deleted file mode 100644 index 3bdc6e3b3c296ecf58d4788924b550d619c00114..0000000000000000000000000000000000000000 --- a/apps/roster/src/protocol/channel/roster_channel_history.erl +++ /dev/null @@ -1,47 +0,0 @@ --module(roster_channel_history). --include("roster.hrl"). --include_lib("roster/static/main_text.hrl"). --include_lib("n2o/include/n2o.hrl"). --compile(export_all). - -info(#'History'{status = get, size = Limit, entity_id = Offset, feed = #mqi{feed_id = #muc{name = ChannelId} = ChannelFeed, - query = SearchQuery, status = FilterStatus}} = RequestData, Req, #cx{params = ClientId} = State) when is_integer(SearchQuery) == false -> - roster:info(?MODULE, "~p:History/get:~p", [ClientId, RequestData]), - R = case roster_channel_helper:get_channel_by_id(ChannelId) of - {error, GetChannelError} -> roster_channel_helper:error_response(GetChannelError, RequestData); - {ok, #'Room'{}} -> - case roster_channel_helper:check_user_permissions(ChannelId, ClientId) of - {error, MemberPermissionError} -> roster_channel_helper:error_response(MemberPermissionError, RequestData); - {ok, #'Member'{}} -> - AllChannelMembers = roster:members(ChannelFeed), -%% search by SearchQuery string - SearchChannelMembers = - case SearchQuery of - [] -> AllChannelMembers; - _ -> - lists:foldl(fun(#'Member'{names = Name, surnames = Surname, alias = Nick} = Member, Acc) -> - case roster_channel_helper:startswith(Name, SearchQuery) or roster_channel_helper:startswith(Surname, SearchQuery) or roster_channel_helper:startswith(Nick, SearchQuery) of true -> Acc ++ [Member]; _ -> Acc end - end, [], AllChannelMembers) - end, -%% Filter members by status: -%% admin -> admin + owner -%% member -> member -%% removed -> removed -%% [] -> all members - AcceptedValues = case FilterStatus of admin -> [admin, owner]; [] -> []; _ -> [FilterStatus] end, - FilteredMembers = case AcceptedValues of - [] -> SearchChannelMembers; - _ -> lists:filter(fun(#'Member'{status = MemberStatus}) -> lists:member(MemberStatus, AcceptedValues) end, SearchChannelMembers) end, -%% sort members by first_name (Member.names) - SortedMembers = lists:sort(fun(A, B) -> A#'Member'.names < B#'Member'.names end, FilteredMembers), -%% paginate members - get list slice - PaginatedMembers = roster_channel_helper:paginator(SortedMembers, Limit, Offset), - RequestData#'History'{data = PaginatedMembers} - end - end, - roster:info(?MODULE, "History/get.Response:~p", [R]), - {reply, {bert, R}, Req, State}; - -info(#'History'{} = RequestData, Req, #cx{params = ClientId} = State) -> - roster:info(?MODULE, "~p:History/unknown:~p", [ClientId, RequestData]), - {reply, {bert, roster_channel_helper:error_response_400(RequestData)}, Req, State}. \ No newline at end of file diff --git a/apps/roster/src/protocol/channel/roster_channel_link.erl b/apps/roster/src/protocol/channel/roster_channel_link.erl deleted file mode 100644 index 1be559dbfe659f14a3f11a4f9f6a97549e47e42e..0000000000000000000000000000000000000000 --- a/apps/roster/src/protocol/channel/roster_channel_link.erl +++ /dev/null @@ -1,117 +0,0 @@ --module(roster_channel_link). --include("roster.hrl"). --include_lib("roster/static/room_channel_text.hrl"). --include_lib("roster/static/room_channel_var.hrl"). --include_lib("roster/static/main_text.hrl"). --include_lib("n2o/include/n2o.hrl"). --compile(export_all). - -info(#'Link'{status = gen} = RequestData, Req, #cx{params = ClientId} = State) -> - roster:info(?MODULE, "~p:Link/generate:~p", [ClientId, RequestData]), - GeneratedLink = roster_channel_helper:generate_link(), - roster:info(?MODULE, "Link/generate.Response:~p", [GeneratedLink]), - {reply, {bert, RequestData#'Link'{name = GeneratedLink}}, Req, State}; - -info(#'Link'{status = check, name = Link, room_id = ChannelId} = RequestData, Req, #cx{params = ClientId} = State) when Link /= [] -> - roster:info(?MODULE, "~p:Link/check:~p", [ClientId, RequestData]), - R = case roster_channel_helper:validate_link_format(Link) of - {error, LinkValidationError} -> roster_channel_helper:error_response(LinkValidationError, RequestData); - {ok, _} -> - case roster_channel_helper:check_link_availability(Link, ChannelId) of - {error, LinkAvailabilityError} -> roster_channel_helper:error_response(LinkAvailabilityError, RequestData); - {ok, _} -> RequestData - end - end, - roster:info(?MODULE, "Link/check.Response:~p", [R]), - {reply, {bert, R}, Req, State}; - -info(#'Link'{status = add, name = Link, room_id = ChannelId} = RequestData, Req, #cx{params = ClientId, - client_pid = C} = State)when Link /= [] andalso ChannelId /= [] -> - roster:info(?MODULE, "~p:Link/add:~p", [ClientId, RequestData]), - R = case roster_channel_helper:get_channel_by_id(ChannelId) of - {error, GetChannelError} -> roster_channel_helper:error_response(GetChannelError, RequestData); - {ok, #'Room'{links = StoredLinks} = Channel} -> - case roster_channel_helper:check_user_permissions(ChannelId, ClientId, ?FKC_LINK_MNGT) of - {error, MemberPermissionError} -> roster_channel_helper:error_response(MemberPermissionError, RequestData); - {ok, _} -> case roster_channel_helper:validate_link_format(Link) of - {error, LinkValidationError} -> roster_channel_helper:error_response(LinkValidationError, RequestData); - {ok, _} -> case roster_channel_helper:check_link_add_availability(Channel) of - {error, LinksCountError} -> roster_channel_helper:error_response(LinksCountError, RequestData); - {ok, _} -> case roster_channel_helper:check_link_availability(Link, ChannelId) of - {error, LinkAvailabilityError} -> roster_channel_helper:error_response(LinkAvailabilityError, RequestData); - {ok, _} -> - roster_channel_helper:add_link_index(Link, ChannelId), - UpdLinks = lists:keystore(Link, #'Link'.name, StoredLinks, #'Link'{id = roster_channel_helper:generate_link_id(ChannelId), name = Link, created = roster:now_msec()}), - UpdChannel = Channel#'Room'{links = UpdLinks, status = settings, update = roster:now_msec()}, - kvs:put(UpdChannel), - roster_channel_helper:send_channel(C, UpdChannel), - <<>> - end - end - end - end - end, - roster:info(?MODULE, "Link/add.Response:~p", [R]), - {reply, {bert, R}, Req, State}; - -info(#'Link'{status = delete, id = LinkId, room_id = ChannelId} = RequestData, Req, #cx{params = ClientId, - client_pid = C} = State) when LinkId /= [] andalso ChannelId /= [] -> - roster:info(?MODULE, "~p:Link/delete:~p", [ClientId, RequestData]), - R = case roster_channel_helper:get_channel_by_id(ChannelId) of - {error, GetChannelError} -> roster_channel_helper:error_response(GetChannelError, RequestData); - {ok, #'Room'{links = StoredLinks} = Channel} -> - case roster_channel_helper:check_user_permissions(ChannelId, ClientId, ?FKC_LINK_MNGT) of - {error, MemberPermissionError} -> roster_channel_helper:error_response(MemberPermissionError, RequestData); - {ok, _} -> -%% check is link exists in the Channel - case roster_channel_helper:get_link_by_id(LinkId, StoredLinks) of - {error, LinkNotFoundError} -> roster_channel_helper:error_response(LinkNotFoundError, RequestData); - {ok, #'Link'{name = Link}} -> - UpdLinks = lists:keydelete(LinkId, #'Link'.id, StoredLinks), - UpdChannel = Channel#'Room'{links = UpdLinks, status = settings, update = roster:now_msec()}, - kvs:put(UpdChannel), - roster_channel_helper:delete_link_index(Link), - roster_channel_helper:send_channel(C, UpdChannel), - <<>> - end - end - end, - roster:info(?MODULE, "Link/delete.Response:~p", [R]), - {reply, {bert, R}, Req, State}; - -info(#'Link'{status = update, id = LinkId, name = Link, room_id = ChannelId} = RequestData, Req, #cx{params = ClientId, - client_pid = C} = State) when LinkId /= [] andalso Link /= [] andalso ChannelId /= [] -> - roster:info(?MODULE, "~p:Link/update:~p", [ClientId, RequestData]), - R = case roster_channel_helper:get_channel_by_id(ChannelId) of - {error, GetChannelError} -> roster_channel_helper:error_response(GetChannelError, RequestData); - {ok, #'Room'{links = StoredLinks} = Channel} -> - case roster_channel_helper:check_user_permissions(ChannelId, ClientId, ?FKC_LINK_MNGT) of - {error, MemberPermissionError} -> roster_channel_helper:error_response(MemberPermissionError, RequestData); - {ok, _} -> case roster_channel_helper:validate_link_format(Link) of - {error, LinkValidationError} -> roster_channel_helper:error_response(LinkValidationError, RequestData); - {ok, _} -> - case roster_channel_helper:check_link_availability(Link, ChannelId) of - {error, LinkAvailabilityError} -> roster_channel_helper:error_response(LinkAvailabilityError, RequestData); - {ok, _} -> -%% check is link exists in the Channel - case roster_channel_helper:get_link_by_id(LinkId, StoredLinks) of - {error, LinkNotFoundError} -> roster_channel_helper:error_response(LinkNotFoundError, RequestData); - {ok, #'Link'{id = StoredLinkId, name = OldLink} = StoredLink} -> - UpdLinks = lists:keystore(StoredLinkId, #'Link'.id, StoredLinks, StoredLink#'Link'{name = Link, created = roster:now_msec()}), - UpdChannel = Channel#'Room'{links = UpdLinks, status = settings, update = roster:now_msec()}, - kvs:put(UpdChannel), - roster_channel_helper:delete_link_index(OldLink), - roster_channel_helper:add_link_index(Link, ChannelId), - roster_channel_helper:send_channel(C, UpdChannel), - <<>> - end - end - end - end - end, - roster:info(?MODULE, "Link/update.Response:~p", [R]), - {reply, {bert, R}, Req, State}; - -info(#'Link'{} = RequestData, Req, #cx{params = ClientId} = State) -> - roster:info(?MODULE, "~p:Link/unknown:~p", [ClientId, RequestData]), - {reply, {bert, roster_channel_helper:error_response_400(RequestData)}, Req, State}. \ No newline at end of file diff --git a/apps/roster/src/protocol/channel/roster_channel_member.erl b/apps/roster/src/protocol/channel/roster_channel_member.erl deleted file mode 100644 index 2e850e2cfc1102fb0799a426b10b25bcece6f7da..0000000000000000000000000000000000000000 --- a/apps/roster/src/protocol/channel/roster_channel_member.erl +++ /dev/null @@ -1,131 +0,0 @@ --module(roster_channel_member). --include("roster.hrl"). --include_lib("roster/static/room_channel_text.hrl"). --include_lib("roster/static/room_channel_var.hrl"). --include_lib("roster/static/main_text.hrl"). --include_lib("n2o/include/n2o.hrl"). --compile(export_all). - -info(#'Member'{status = join, feed_id = #muc{name = ChannelId}} = RequestData, Req, - #cx{params = ClientId, client_pid = ClientPid} = State) when ChannelId /= [] -> -%% join channel by member - roster:info(?MODULE, "~p:Member/join:~p", [ClientId, RequestData]), - R = case roster_channel_helper:get_channel_by_id(ChannelId) of - {error, GetChannelError} -> roster_channel_helper:error_response(GetChannelError, RequestData); - {ok, Channel} -> -%% add members to channel - roster_channel_helper:add_members(Channel, [RequestData#'Member'{phone_id = roster:phone_id(ClientId)}]), -%% update subscribers count - ChannelWithSubscribers = roster_channel_helper:set_count_features(Channel), - UpdatedChannel = ChannelWithSubscribers#'Room'{update = roster:now_msec(), status = add}, - kvs:put(UpdatedChannel), - roster_channel_helper:update_members_roster(UpdatedChannel), - roster_channel_helper:send_channel(ClientPid, UpdatedChannel), - <<>> - end, - roster:info(?MODULE, "Member/join.Response:~p", [R]), - {reply, {bert, R}, Req, State}; - -info(#'Member'{status = patch, id = MemId} = RequestData, Req, #cx{params = ClientId, client_pid = ClientPid} = State) -> -%% patch Member info -%% NOTE! For now Member can patch only Member.settings field - roster:info(?MODULE, "~p:Member/patch:~p", [ClientId, RequestData]), - ActedUserPhoneId = roster:phone_id(ClientId), - R = case kvs:get('Member', MemId) of - {ok, #'Member'{phone_id = ActedUserPhoneId} = MemToUpd} -> - UpdMember = roster:patch_member(MemToUpd, RequestData, ?CHANNEL_MEMBER_UPDATE_EXCLUDED_FIELDS), - kvs:put(UpdMember), - roster:send_ses(ClientPid, roster:phone(ActedUserPhoneId), UpdMember#'Member'{status = patch}), - <<>>; - {ok, #'Member'{}} -> roster_channel_helper:error_response_403(RequestData); - #error{} -> roster_channel_helper:error_response(?ERROR_MEMBER_NOT_FOUND, RequestData) - end, - roster:info(?MODULE, "Member/patch.Response:~p", [R]), - {reply, {bert, R}, Req, State}; - -info(#'Member'{status = admin, id = MemberId, feed_id = #muc{name = ChannelId}, settings = PermList} = RequestData, Req, - #cx{params = ClientId, client_pid = ClientPid} = State) -> -%% promote existing member to admin status or update existing admin member settings list - roster:info(?MODULE, "~p:Member/admin:~p", [ClientId, RequestData]), - R = case roster_channel_helper:get_channel_by_id(ChannelId) of - {error, GetChannelError} -> roster_channel_helper:error_response(GetChannelError, RequestData); - {ok, Channel} -> - case roster_channel_helper:check_user_permissions(ChannelId, ClientId, ?FKC_SUBSCR_MNGT) of - {error, MemberPermissionError} -> roster_channel_helper:error_response(MemberPermissionError, RequestData); - {ok, #'Member'{phone_id = ActedMemberPhoneId}} -> -%% check promoted member existing - case kvs:get('Member', MemberId) of - {error, not_found} -> roster_channel_helper:error_response(?ERROR_MEMBER_NOT_FOUND, RequestData); - {ok, #'Member'{status = PromotedMemberStatus, phone_id = PromotedMemberPhoneId} = PromotedMember} -> - case PromotedMemberStatus of - PMS when PMS /= admin andalso PMS /= member -> roster_channel_helper:error_response_403(RequestData); - _ -> -%% update PromotedMember, set status to admin and PermList - UpdMember = PromotedMember#'Member'{status = admin, settings = PermList, update = roster:now_msec()}, - kvs:put(UpdMember), -%% notify acted member - roster:send_ses(ClientPid, ActedMemberPhoneId, UpdMember), -%% notify promoted member - roster:send_ses(ClientPid, PromotedMemberPhoneId, UpdMember), -%% update subscribers count - ChannelWithSubscribers = roster_channel_helper:set_count_features(Channel), - UpdatedChannel = ChannelWithSubscribers#'Room'{update = roster:now_msec(), status = settings}, - kvs:put(UpdatedChannel), - roster_channel_helper:update_members_roster(UpdatedChannel), - roster_channel_helper:send_admin(ClientPid, UpdatedChannel), - <<>> - end - end - end - end, - roster:info(?MODULE, "Member/admin.Response:~p", [R]), - {reply, {bert, R}, Req, State}; - -info(#'Member'{status = member, id = MemberId, feed_id = #muc{name = ChannelId}} = RequestData, Req, - #cx{params = ClientId, client_pid = ClientPid} = State) -> -%% demote admin to ordinary member, delete all admin permissions from settings - roster:info(?MODULE, "~p:Member/member:~p", [ClientId, RequestData]), - R = case roster_channel_helper:get_channel_by_id(ChannelId) of - {error, GetChannelError} -> roster_channel_helper:error_response(GetChannelError, RequestData); - {ok, Channel} -> - case roster_channel_helper:check_user_permissions(ChannelId, ClientId, ?FKC_SUBSCR_MNGT) of - {error, MemberPermissionError} -> roster_channel_helper:error_response(MemberPermissionError, RequestData); - {ok, #'Member'{phone_id = ActedMemberPhoneId}} -> -%% check demoted member existing - case kvs:get('Member', MemberId) of - {error, not_found} -> roster_channel_helper:error_response(?ERROR_MEMBER_NOT_FOUND, RequestData); - {ok, #'Member'{status = DemotedMemberStatus, phone_id = DemotedMemberPhoneId, - settings = DemotedMemberSettings} = DemotedMember} -> - case DemotedMemberStatus of - DMS when DMS /= admin -> roster_channel_helper:error_response_403(RequestData); - admin -> -%% update DemotedMember, set status to member and update settings (set all admin settings to false) - UpdPermList = lists:flatten([begin - case FeatureGroup of - ?FGC_ADMIN_PERMISSIONS -> Feature#'Feature'{value = false}; - _ -> Feature - end - end || #'Feature'{group = FeatureGroup} = Feature <- DemotedMemberSettings]), - UpdMember = DemotedMember#'Member'{status = member, settings = UpdPermList, update = roster:now_msec()}, - kvs:put(UpdMember), -%% notify acted member - roster:send_ses(ClientPid, ActedMemberPhoneId, UpdMember), -%% notify promoted member - roster:send_ses(ClientPid, DemotedMemberPhoneId, UpdMember), -%% update subscribers count - ChannelWithSubscribers = roster_channel_helper:set_count_features(Channel), - UpdatedChannel = ChannelWithSubscribers#'Room'{update = roster:now_msec(), status = settings}, - kvs:put(UpdatedChannel), - roster_channel_helper:update_members_roster(UpdatedChannel), - roster_channel_helper:send_admin(ClientPid, UpdatedChannel), - <<>> - end - end - end - end, - roster:info(?MODULE, "Member/member.Response:~p", [R]), - {reply, {bert, R}, Req, State}; - -info(#'Member'{} = RequestData, Req, #cx{params = ClientId} = State) -> - roster:info(?MODULE, "~p:Member/unknown:~p", [ClientId, RequestData]), - {reply, {bert, roster_channel_helper:error_response_400(RequestData)}, Req, State}. \ No newline at end of file diff --git a/apps/roster/src/protocol/channel/roster_channel_message.erl b/apps/roster/src/protocol/channel/roster_channel_message.erl deleted file mode 100644 index 1d348dedb1cb3015708d267682209e30abbfb985..0000000000000000000000000000000000000000 --- a/apps/roster/src/protocol/channel/roster_channel_message.erl +++ /dev/null @@ -1,46 +0,0 @@ --module(roster_channel_message). --include("roster.hrl"). --include_lib("roster/static/room_channel_text.hrl"). --include_lib("roster/static/room_channel_var.hrl"). --include_lib("roster/static/main_text.hrl"). --include_lib("n2o/include/n2o.hrl"). --compile(export_all). - -info(#'Message'{feed_id = #muc{name = ChannelId}, from = FromPhoneId, status = MsgStatus} = RequestData, Req, - #cx{params = ClientId} = State) when MsgStatus == edit; MsgStatus == delete -> -%% Only channel owner and admin with permission "EDIT_MSG" can edit others' messages -%% Only channel owner and admin with permission "DELETE_MSG" can delete others' messages - roster:info(?MODULE, "~p:ChannelMsg.Edit.Delete/CheckPermissions:~p", [ClientId, RequestData]), - PermToCheck = case MsgStatus of edit -> ?FKC_EDIT_MSG; delete -> ?FKC_DELETE_MSG end, - R = case roster_channel_helper:get_channel_by_id(ChannelId) of - {error, GetChannelError} -> {error, roster_channel_helper:error_response(GetChannelError, RequestData)}; - {ok, _} -> case roster:phone_id(ClientId) of - FromPhoneId -> {ok, []}; - _ -> case roster_channel_helper:check_user_permissions(ChannelId, ClientId, PermToCheck) of - {error, MemberPermissionError} -> {error, roster_channel_helper:error_response(MemberPermissionError, RequestData)}; - {ok, _} -> {ok, []} - end - end - end, - case R of - {error, Er} -> roster:info(?MODULE, "ChannelMsg.Edit.Delete/CheckPermissions.Response:~p", [Er]), {reply, {bert, Er}, Req, State}; - {ok, _} -> roster_message:info(RequestData, Req, State) - end; - -info(#'Message'{feed_id = #muc{name = ChannelId}} = RequestData, Req, #cx{params = ClientId} = State) -> - roster:info(?MODULE, "~p:ChannelMsg/CheckPermissions:~p", [ClientId, RequestData]), - R = case roster_channel_helper:get_channel_by_id(ChannelId) of - {error, GetChannelError} -> {error, roster_channel_helper:error_response(GetChannelError, RequestData)}; - {ok, _} -> case roster_channel_helper:check_user_permissions(ChannelId, ClientId, ?FKC_POST_MSG) of - {error, MemberPermissionError} -> {error, roster_channel_helper:error_response(MemberPermissionError, RequestData)}; - {ok, _} -> {ok, []} - end - end, - case R of - {error, Er} -> roster:info(?MODULE, "ChannelMsg/CheckPermissions.Response:~p", [Er]), {reply, {bert, Er}, Req, State}; - {ok, _} -> roster_message:info(RequestData, Req, State) - end; - -info(#'Message'{} = RequestData, Req, #cx{params = ClientId} = State) -> - roster:info(?MODULE, "~p:ChannelMessage/unknown:~p", [ClientId, RequestData]), - {reply, {bert, roster_channel_helper:error_response_400(RequestData)}, Req, State}. \ No newline at end of file diff --git a/apps/roster/src/protocol/micro_auth.erl b/apps/roster/src/protocol/micro_auth.erl new file mode 100644 index 0000000000000000000000000000000000000000..eba035dfdd96a4b376db42eb3da7b0544f5cc9f2 --- /dev/null +++ b/apps/roster/src/protocol/micro_auth.erl @@ -0,0 +1,161 @@ +-module(micro_auth). +-include("roster.hrl"). +-include("micro.hrl"). +-include("static_auth.hrl"). +%-include_lib("bpe/include/bpe.hrl"). +-include_lib("emqttd/include/emqttd.hrl"). +-include_lib("n2o/include/n2o.hrl"). +-behaviour(emqttd_auth_mod). +-compile(export_all). + +start() -> + n2o_async:start(#handler{module = micro_auth, class = system, group = roster, name = micro_auth, state = []}). + +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}]), ignore; +check(#mqtt_client{client_id = <<"emqttd_", _/binary>> = ClientId, username = <<"micro">>, + client_pid = ClientPid, will_topic = _WT, peername = {_Ip, _}} = _MC, Token, _State) -> + roster:info(?MODULE, "~p:Auth:auth(micro)/check", [ClientId]), + M = application:get_env(roster, validate_token, ?MODULE), + case M:validate_token(Token) of + {ok, AccUuid} -> + emqttd_client:subscribe(ClientPid, [{roster:action_topic(ClientId), 2}]), + PhoneId = micro:uuid_to_id('LinkRoster', AccUuid), + AuthPid = n2o_async:pid(system, ?MODULE), + case kvs:get('Auth', ClientId) of + {ok, #'Auth'{user_id = PhoneId} = Auth} -> + AuthPid ! Auth#'Auth'{type = []}, + ok; + {ok, #'Auth'{user_id = _PhoneId2}} -> {error, invalid_account_id}; + {error, _} -> + case PhoneId of + [] -> {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}), + AuthPid ! Auth#'Auth'{client_id = ClientId, user_id = PhoneId, type = verified}, + ok + end + end; + error -> + roster:info(?MODULE, "~p:Auth:auth(micro)/check:invalid_token", [ClientId]), + {error, invalid_token} + end; +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]), + 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}, + kvs:put(SendAuth#'Auth'{type = Type}), %% TODO save token? + roster:send_action(C, ClientId, SendAuth), + SendAuth; + {error, _} -> #io{code = #error{code = session_not_found}, data = ClientId} + end, + {reply, {bert, IO}, Req, State}; +info(#'Auth'{type = clear, client_id = <<"emqttd_", _/binary>> = ClientId} = Auth, _Req, #cx{client_pid = C} = State) -> + Reply = {reply, {bert, IO}, _, _} = roster_auth:info(Auth, State#cx{params = ClientId}), + roster:send_action(C, ClientId, IO), Reply; +info(#'Auth'{type = delete, client_id = <<"emqttd_", _/binary>> = ClientId} = Auth, _Req, #cx{client_pid = C} = State) -> + Reply = {reply, {bert, IO}, _, _} = roster_auth:info(Auth, State#cx{params = ClientId}), + roster:send_action(C, ClientId, IO), Reply; +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:send_action(C, SysClientId, Auth), %%TODO send uuid phone and roster_id to micro? + <<>>; + {error, _} -> #io{code = #error{code = session_not_found}} + end, + {reply, {bert, IO}, Req, State}; + + +info(#'Auth'{phone = Phone} = Auth, Req, #cx{params = ClientId} = State) -> + roster:info(?MODULE, "~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", []), + {ok, Async#handler{state = C, seq = 0}}; + +proc(#'Auth'{type = [], phone = Phone, client_id = <<"emqttd_", _/binary>> = ClientId}, + #handler{state = C} = H) -> + roster:info(?MODULE, "~p:~p:Auth/login", [Phone, ClientId]), + roster_presence:on_connect(Phone, ClientId, C), %%TODO send Auth updated session? + {reply, [], H}; + +proc({disconnect, #'Auth'{client_id = <<"emqttd_", _/binary>>} = Auth}, + #handler{state = C} = H) -> roster_presence:on_disconnect(Auth, C), + {reply, [], H}; + +proc(#'Auth'{type = verified, client_id = <<"emqttd_", _/binary>> = ClientId, phone = Phone, user_id = UserId} = Stored, + #handler{state = C} = H) -> + roster:info(?MODULE, "~p:~p:Auth(micro)/login(created session)", [Phone, ClientId]), + roster_presence:on_verify(ClientId, UserId), + roster_presence:on_connect(Phone, ClientId, C), + {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 auth data :~p", [Unknown]), + {reply, [], H}. + +%% JWT key API %%TODO remove if unuse +%%get_pub_key2() -> +%% Url = application:get_env(roster, pub_key_url, ""), +%% {ok, {{_,200,"OK"}, _, Data}} = httpc:request(Url), +%% #{<<"keys">> := [Key|_]} = jsx:decode(iolist_to_binary(Data), [return_maps]), +%% get_pub_key(Key). +%% +%%get_pub_key() -> +%% Data = <<"{\"kty\":\"EC\",\"use\":\"sig\",\"crv\":\"P-256\",\"kid\":\"20181113\"," +%% "\"x\":\"Fhab_lTiJZV64ed6wcGLLChOngkyy-UdDPKuZdaVbQQ\"," +%% "\"y\":\"FX2wmMiSvbufdusxpqW1iJx-NDdGf0OGqxicnjSAs7s\"}">>, +%% +%%%% dummy data +%%%% Data = <<"{\"keys\":[{\"kty\":\"EC\",\"use\":\"sig\",\"crv\":\"P-256\",\"kid\":\"20181113\",\"x\":\"Fhab_lTiJZV64ed6wcGLLChOngkyy-UdDPKuZdaVbQQ\",\"y\":\"FX2wmMiSvbufdusxpqW1iJx-NDdGf0OGqxicnjSAs7s\"},{\"kty\":\"EC\",\"use\":\"sig\",\"crv\":\"P-256\",\"kid\":\"20180601\",\"x\":\"MKBCTNIcKUSDii11ySs3526iDZ8AiTo7Tu6KPAqv7D4\",\"y\":\"4Etl6SRW2YiLUrN5vfvVHuhp7x8PxltmWWlbbM4IFyM\"},{\"kty\":\"EC\",\"use\":\"sig\",\"crv\":\"P-256\",\"kid\":\"20180608\",\"x\":\"3mdE0rODWRju6qqU01Kw5oPYdNxBOMisFvJFH1vEu9Q\",\"y\":\"VYlYwBfOTIICojCPfdUjnmkpN-g-lzZKxzjAoFmDRm8\"}]}">>, +%%%% Data = <<"{\"keys\":[{\"kty\":\"EC\",\"use\":\"sig\",\"crv\":\"P-256\",\"kid\":\"20181017\"," +%%%% "\"x\":\"qhRT6Pc3c_MDrtwMm9YmlyKfYcIgKPJ8M-mhu5Zi1p0\"," +%%%% "\"y\":\"WUhASGuqrctMhj1fxmkLKPBujUpY7AFMj5pKLUszHNI\"}," +%%%% "{\"kty\":\"EC\",\"use\":\"sig\",\"crv\":\"P-256\",\"kid\":\"20181016\"," +%%%% "\"x\":\"eEQKasOAo80VAFm49hjqxdVbz1qK_XjcmjRevGcEk8Y\",\"y\":\"PWZvYWVT5ZGS3pQKRDokjnuEeNuqKjKB8RMsyQ6KN_U\"}," +%%%% "{\"kty\":\"EC\",\"use\":\"sig\",\"crv\":\"P-256\",\"kid\":\"20181016\"," +%%%% "\"x\":\"Kckzk88vnDUtiU3fl25GHohTgIm0Uw5zrOrS_cwMoKQ\",\"y\":\"nZN5B9-37JimFe7zAeEyztxrGCviQ7tporvi_yDU7r8\"}]}">>, +%% get_pub_key(Data). +%%get_pub_key(<<_/binary>> = Data) -> +%% get_pub_key(jsx:decode(Data, [return_maps])); +%%get_pub_key(#{<<"keys">> := Keys}) -> +%% [get_pub_key(Key) || Key <-Keys]; +%%get_pub_key(#{<<"x">> := X, <<"y">> := Y}) -> <<4, (base64url:decode(X))/binary, (base64url:decode(Y))/binary>>. +%% +%%decode(Token) -> decode(Token, get_pub_key()). +%%decode(Token, Pub) -> +%% jose_jws_compact:decode(Token, <<"ES256">>, Pub, #{parse_signature => base64, parse_payload => map}). +%% +%%acc_id(Token) -> +%% acc_id(Token, get_pub_key()). +%%acc_id(Token, Pub) -> +%% case decode(Token, Pub) of +%% #{<<"sub">> := Sub} -> base64url:decode(Sub); +%% _ -> error +%% end. + +%% +%%get_roster([]) -> []; +%%get_roster(RosterId) when is_integer(RosterId) -> kvs:get('Roster', RosterId); +%%get_roster(<>) -> get_roster(roster_id(AccId)). +%% +%%roster_id(<>) -> +%% case kvs:get('LinkRoster', AccId) of +%% {ok, #'LinkRoster'{phone_id = PhoneId}} -> roster:roster_id(PhoneId); +%% _ -> [] +%% end. diff --git a/apps/roster/src/protocol/micro_profile.erl b/apps/roster/src/protocol/micro_profile.erl new file mode 100644 index 0000000000000000000000000000000000000000..676627a2f1446597d4831244fecc312762a3f165 --- /dev/null +++ b/apps/roster/src/protocol/micro_profile.erl @@ -0,0 +1,57 @@ +-module(micro_profile). +-include("roster.hrl"). +-include("micro.hrl"). +-include_lib("n2o/include/n2o.hrl"). +-compile(export_all). + +info(#'Profile'{phone=LinkPhone, rosters=Rosters, status=create} = Profile, Req, %% TODO are Rosters sent? + #cx{params = ClientId = <<"sys_", _/binary>>} = State) -> + Phone = micro:uuid_to_id('LinkProfile', LinkPhone), + roster:info(?MODULE, "~p:~p:Profile/create:~s", [LinkPhone,ClientId,io_lib:format("~p",[Profile])]), + IO = case kvs:get('Profile', Phone) of + {ok, #'Profile'{}} -> + roster:info(?MODULE, "~p:~p:Profile/already_exist", [LinkPhone,ClientId]), + #io{code = #error{code = already_exist}, data = LinkPhone}; + _ -> + kvs:put(Profile#'Profile'{status = [], rosters = []}), + kvs:put(#'LinkProfile'{id = uuid:to_binary(binary_to_list(LinkPhone)), phone = LinkPhone}), + Res = lists:flatten([begin + {reply, {bert, IO}, _, _} = micro_roster:info(Roster#'Roster'{status = add}, Req, State), + case IO of #io{} -> IO#io{data = LinkPhone}; #'Roster'{} -> [] end + end || #'Roster'{} = Roster <- Rosters]), + case Res of [] -> Profile; [Err|_] -> Err end + end, + {reply, {bert, IO}, 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])]), + IO = case kvs:get('Profile', Phone) of + {ok, #'Profile'{phone = Phone} = StoredProfile} -> + P = roster:patch_profile(StoredProfile, Profile), + kvs:put(P#'Profile'{status = []}), + roster:send_ses(C, Phone, P#'Profile'{status = update}), + P#'Profile'{phone = LinkPhone, status = update}; + {error, _} -> + #io{code = #error{code = profile_not_found}, data = LinkPhone} + end, + {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])]), + 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, #'Profile'{phone=UID, rosters = NewRosters}}=P, 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))], + {reply, {bert, P#'Profile'{rosters = Accs}}, Req, State} + end; + + +info(#'Profile'{phone = LinkPhone} = Profile, Req, #cx{params = ClientId} = State) -> + roster:info(?MODULE, "~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 new file mode 100644 index 0000000000000000000000000000000000000000..208d831858eb4df6c8248f8c84fbfa44f897b7b7 --- /dev/null +++ b/apps/roster/src/protocol/micro_roster.erl @@ -0,0 +1,43 @@ +-module(micro_roster). +-include("roster.hrl"). +-include("micro.hrl"). +-include_lib("n2o/include/n2o.hrl"). +-compile(export_all). + +start() -> n2o_async:start(#handler{module = ?MODULE, class = system, group = roster, name = ?MODULE, state = []}). + +info(#'Roster'{status = add, phone = LinkPhone, id = LinkRosterId} = Roster, Req, + #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])]), + IO = case kvs:get('Profile', Phone) of + {ok, #'Profile'{rosters = RosterIds} = Profile} -> + RosterId = roster:roster_id(PhoneId), + case is_integer(RosterId) andalso RosterId > 0 of + true -> case lists:member(RosterId, RosterIds) of + true -> #error{code = account_already_exist}; + _ -> #error{code = mismatch_user_data} + end; + false -> + NewRosterId = roster:new_roster(Phone), + kvs:put(Profile#'Profile'{phone = Phone, + rosters = RosterIds++[NewRosterId], update = roster:now_msec()}), + kvs:put(#'LinkRoster'{id = micro:uuid_to_bin(LinkRosterId), phone_id = roster:phone_id(Phone, NewRosterId)}), + Roster + end; + #error{} -> #io{code = #error{code = profile_not_found}} + end, + {reply, {bert, IO}, Req, State#cx{state = []}}; +info(#'Roster'{status = update, id = LinkRosterId} = Roster, Req, + #cx{params = <<"sys_", _/binary>> = ClientId} = State) -> + PhoneId = micro:uuid_to_id('LinkRoster', LinkRosterId), + roster:info(?MODULE, "~p:~p:Roster(micro)/update:~s", [LinkRosterId, ClientId, io_lib:format("~p", [Roster])]), + {reply, {bert, IO}, _, _} + = roster_roster:info(Roster#'Roster'{id = roster:roster_id(PhoneId), status = patch}, Req, State), + IO2 = case IO of <<>> -> Roster#'Roster'{update = roster:now_msec()}; #'Roster'{ }=R ->R#'Roster'{ status=update}; _ -> IO 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]), + {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 41040c8592803ae5c266efe0d63eef173c9b515b..570bea6ef9a2b58ad953289146fff8d0f8ffd387 100644 --- a/apps/roster/src/protocol/roster_acl.erl +++ b/apps/roster/src/protocol/roster_acl.erl @@ -12,7 +12,7 @@ -define(ACL_RULE_TAB, roster_acl_rule). --record(state, {config, nomatch = allow, vnodes = []}). +-record(state, {config, nomatch = deny, vnodes = []}). %%-------------------------------------------------------------------- %% API %%-------------------------------------------------------------------- @@ -72,7 +72,7 @@ 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, Topic} = Who, #state{}) -> +check_acl({#mqtt_client{client_id = <<"emqttd_", _/binary>> = Client}, publish, <<"events/", _/binary>>} = Who, #state{}) -> case kvs:get('Auth', Client) of {ok, #'Auth'{type = expired}} -> deny; {ok, _} -> allow; diff --git a/apps/roster/src/protocol/roster_auth.erl b/apps/roster/src/protocol/roster_auth.erl index cdd9072303625afc53fa36108c8e052324513a50..9d979e93758c31594765a58d7e3006f1b966ef1c 100644 --- a/apps/roster/src/protocol/roster_auth.erl +++ b/apps/roster/src/protocol/roster_auth.erl @@ -17,28 +17,36 @@ start() -> n2o_async:start(#handler{module = roster_auth, class = system, group = roster, name = roster_auth, state = []}). +stop() -> + n2o_async:stop(system, roster_auth). init([Listeners]) -> {ok, Listeners}. description() -> "Roster Authentication Module". +check(#mqtt_client{client_id = <<"nagios-", _/binary>> = ClientId, username = <<"api">>}, _, _) -> + roster:info(?MODULE, "~p:Nagios/test", [ClientId]), + ignore; + check(#mqtt_client{client_id = <<"reg_", _/binary>> = ClientId, username = <<"api">>, client_pid = ClientPid, will_topic = WT}, _, _) -> roster:info(?MODULE, "~p:Auth:reg/check", [ClientId]), - case WT of ?VERSION -> emqttd_client:subscribe(ClientPid, [{roster:action_topic(ClientId), 2}]), ignore; + case WT of <<"version/",BVer/binary>> -> emqttd_client:subscribe(ClientPid, [{roster:action_topic(ClientId), 2}]), ignore; _ -> {error, invalid_version} end; check(#mqtt_client{client_id = <<"emqttd_", _/binary>> = ClientId, username = <<"api">>, - client_pid = ClientPid, will_topic = WT, peername = {Ip, _}} = MC, Token, State) -> - roster:info(?MODULE, "~p:Auth:auth/check:~p", [ClientId, lists:sublist(binary_to_list(Token), 16)]), + client_pid = ClientPid, will_topic = WT, peername = {Ip, _}, ws_initial_headers = Headers} = MC, Token, State) -> + roster:info(?MODULE, "[WS Headers]: ~p", [Headers]), + roster:info(?MODULE, "~p:Auth:auth/check:~p~p", [ClientId, lists:sublist(binary_to_list(Token), 16),WT]), case case State of #'Auth'{} -> {ok, State}; _ -> kvs:get('Auth', ClientId) end of {ok, #'Auth'{type = Type, token = Token, settings = Settings, last_online = LO} = Auth} when Type == verified; Type == []; Type == expired -> - case WT of - ?VERSION -> emqttd_client:subscribe(ClientPid, [{roster:action_topic(ClientId), 2}]), + % roster:info(?MODULE, "Auth:auth/check:~p", [Auth]), + case WT of + <<"version/",BVer/binary>> -> emqttd_client:subscribe(ClientPid, [{roster:action_topic(ClientId), 2}]), IpF = #'Feature'{id = <>, key = ?IP_KEY, value = IpBin = roster:ip_to_list(Ip), group = ?AUTH_GROUP}, Settings2 = lists:foldl(fun(#'Feature'{key = K} = F, Acc) -> lists:keystore(K, #'Feature'.key, Acc, F) end, Settings, [IpF | get_geolocal(IpBin, ClientId)]), - AuthPid = n2o_async:pid(system, ?MODULE), Now = roster:now_msec(), + AuthPid = roster:n2o_pid(?MODULE), Now = roster:now_msec(), LastOnline = case Type of verified -> Now; _ -> LO end, Auth2 = Auth#'Auth'{type = update, settings = Settings2, created = Now, last_online = LastOnline}, case {roster:parse_token(Token), Type} of @@ -50,13 +58,15 @@ check(#mqtt_client{client_id = <<"emqttd_", _/binary>> = ClientId, username = << OldIpBin = case FindIp = lists:keyfind(?IP_KEY, #'Feature'.key, Settings) of false -> IpBin; #'Feature'{value = V} -> V end, IsCheckIp = application:get_env(roster, auth_check_ip, true), + Ver=binary_to_list(BVer), Auth4 = case IpBin of OldIpBin when FindIp == false -> - kvs:put(Auth3 = Auth2#'Auth'{type = Type}), Auth3; - OldIpBin -> Auth; + kvs:put(Auth3 = Auth2#'Auth'{type = Type}), control_ver(Auth3,Ver); + OldIpBin -> control_ver(Auth,Ver); _ when IsCheckIp -> kvs:put(Auth2#'Auth'{type = expired}), Auth2; - _ -> Auth end, + _ -> control_ver(Auth,Ver) + end, AuthPid ! Auth4, ok end; _ -> {error, invalid_version} end; @@ -77,6 +87,9 @@ check(#mqtt_client{client_id = <<"emqttd_", _/binary>> = ClientId, username = << {error, mismatch_user_data} end end; + Error404 = {error, not_found} -> + roster:error(?MODULE, "~p:Auth/check:~p", [ClientId, Error404]), + Error404; Err -> roster:error(?MODULE, "~p:Auth:auth/check:~p", [ClientId, Err]), Err end; check(_Client, _Password, _Opts) -> ignore. @@ -114,7 +127,7 @@ info(#'Auth'{type = reg, token = [], services = Services, phone = Phone} = Auth, roster:info(?MODULE, "~p:~p:Auth/reg~p:~p", [Phone, RegClientId, Services, lists:sublist(binary_to_list(Codes), 16)]), case Services of - [] -> n2o_async:pid(system, ?MODULE) ! {sms, Phone, Codes}, {ok, sms_sent}; + [] -> roster:n2o_pid(?MODULE) ! {sms, Phone, Codes}, {ok, sms_sent}; [jwt] -> {ok2, jwt_sent, Codes} end end @@ -127,11 +140,11 @@ info(#'Auth'{type = resend, token = [], dev_key = DevKey, phone = Phone}, Req, {reply, {bert, #io{code = case kvs:get('Auth', RegClientId) of {ok, #'Auth'{attempts = 0, phone = Phone, dev_key = DevKey} = Stored} -> {ok, Codes} = telesign_api:generate_sms(Phone), - n2o_async:pid(system, ?MODULE) ! {sms, Phone, Codes}, + roster:n2o_pid(?MODULE) ! {sms, Phone, Codes}, kvs:put(Stored#'Auth'{sms_code = Codes, attempts = ?MAX_ATTEMPTS}), #ok{code = sms_sent}; {ok, #'Auth'{sms_code = SMS, phone = Phone, dev_key = DevKey}} -> - n2o_async:pid(system, ?MODULE) ! {sms, Phone, SMS}, #ok{code = sms_sent}; + roster:n2o_pid(?MODULE) ! {sms, Phone, SMS}, #ok{code = sms_sent}; {ok, #'Auth'{}} -> #error{code = mismatch_user_data}; #error{} -> #error{code = session_not_found} end}}, Req, State}; @@ -140,7 +153,7 @@ info(#'Auth'{type = voice, token = [], services = [Lang], phone = Phone, dev_key roster:info(?MODULE, "~p:~p:Auth/voice", [Phone, RegClientId]), {reply, {bert, #io{code = case kvs:get('Auth', RegClientId) of {ok, #'Auth'{sms_code = SMS, phone = Phone, dev_key = DevKey}} -> - n2o_async:pid(system, ?MODULE) ! {voice, Phone, SMS, Lang}, + roster:n2o_pid(?MODULE) ! {voice, Phone, SMS, Lang}, #ok{code = call_in_progress}; {ok, #'Auth'{}} -> #error{code = mismatch_user_data}; #error{} -> #error{code = session_not_found} end}}, Req, State}; @@ -167,13 +180,14 @@ info(#'Auth'{type = verify, phone = Phone, dev_key = DevKey, settings = Settings || #'Feature'{key = Key} = F <-Settings], kvs:put(P = #'Profile'{phone = Phone, rosters = [Roster], settings = NewSettings, update = roster:now_msec()}), - n2o_async:pid(system, ?MODULE) ! {vox, P, client(RegClientId)}, + roster:n2o_pid(?MODULE) ! {vox, P, client(RegClientId)}, {roster, Roster} end, UserId = roster:phone_id(Phone, RosterId), ClientId = client(RegClientId), [kvs:delete('Auth', ClId) || #'Auth'{client_id = ClId} <- kvs:index('Auth', dev_key, DevKey)], {'Token', Token} = roster:gen_token([], []), + Now = roster:now_msec(), kvs:put(Stored#'Auth'{type = verified, client_id = ClientId, token = Token, - sms_code = [], attempts = [], user_id = UserId, created = roster:now_msec()}), + sms_code = [], attempts = [], user_id = UserId, created = Now, last_online = Now}), #io{code = #ok2{code = login, src = {ClientId, Token}}}; {error, Err} -> #io{code = #error{code = Err}} end; {ok, #'Auth'{attempts = 0, phone = Phone, dev_key = DevKey}} -> @@ -252,31 +266,39 @@ info(#'Auth'{phone = Phone} = Auth, Req, #cx{params = ClientId} = State) -> proc(init, #handler{name = roster_auth} = Async) -> {ok, C} = emqttc:start_link([{client_id, <<"roster_auth">>}, {logger, {console, error}}, {reconnect, 5}]), _ = locus:start_loader(city, application:get_env(locus, url, ?GEO_URL)), - case locus:wait_for_loader(city, 30000) of {ok, _} -> ok; + case locus:wait_for_loader(city, 10000) of {ok, _} -> ok; Err -> roster:error(?MODULE, "error:~p:locus", [Err]) end, - roster:info(?MODULE, "ASYNC AUTH PROC started", []), + roster:info(?MODULE, "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) -> - Reply = proc(Stored#'Auth'{type = []}, H), +%%proc(#'Auth'{type = verified, client_id = <<"emqttd_", Post/binary>> = ClientId, user_id = UserId} = Stored, #handler{} = H) -> +%% proc(Stored#'Auth'{type = {reg,?VERSION}}, H); +proc(#'Auth'{type = {reg,Ver}, client_id = <<"emqttd_", Post/binary>> = ClientId, user_id = UserId} = Stored, #handler{} = H) -> + Reply = proc(Stored#'Auth'{type = {ver,Ver}}, H), RegClientId = <<"reg_", Post/binary>>, - n2o:unsubscribe(RegClientId, roster:action_topic(RegClientId)), + n2o_vnode:unsubscribe(RegClientId, roster:action_topic(RegClientId)), kvs:delete(mqtt_session, RegClientId), roster_presence:on_verify(ClientId, UserId), Reply; -proc(#'Auth'{type = [], token = Token, phone = Phone, client_id = <<"emqttd_", _/binary>> = ClientId}, +%%proc(#'Auth'{type = [], token = Token, phone = Phone, client_id = <<"emqttd_", _/binary>> = ClientId}=Auth,H)-> +%% proc(Auth#'Auth'{type = {ver,?VERSION}}, H); +proc(#'Auth'{type = {ver,Ver}, token = Token, phone = Phone, client_id = <<"emqttd_", _/binary>> = ClientId}, #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)]), - roster_presence:on_connect(Phone, ClientId, C), - {reply, [], H}; + roster_presence:on_connect(Phone, ClientId, NewC, Ver), + {reply, [], H#handler{state = NewC}}; proc(#'Auth'{type = update, client_id = <<"emqttd_", _/binary>> = ClientId} = Auth, #handler{state = C} = H) -> {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)]), - n2o_vnode:send(C, roster:action_topic(ClientId), term_to_binary(Auth#'Auth'{token = Token2}), [{qos, 0}]), - {reply, [], H}; -proc({disconnect, #'Auth'{client_id = <<"emqttd_", _/binary>>} = Auth}, - #handler{state = C} = H) -> roster_presence:on_disconnect(Auth, C), - {reply, [], H}; + n2o_vnode:send(NewC, roster:action_topic(ClientId), term_to_binary(Auth#'Auth'{token = Token2}), [{qos, 0}]), + {reply, [], H#handler{state = NewC}}; +proc({disconnect, #'Auth'{client_id = <<"emqttd_", _/binary>>} = Auth}, #handler{state = C} = H) -> +% NewC=roster:restart_client(C,<<"roster_auth">>), +% spawn(roster, client_pid, [C,<<"roster_auth">>]), + roster_presence:on_disconnect(Auth, C), + {reply, [], H#handler{state = C}}; proc({vox, #'Profile'{phone = Phone, services = Services} = Profile, Cli}, #handler{state = C} = H) -> {VoxStatus, VoxResponse} = vox_api:create_voximplant_user(Phone), Service = case VoxStatus of @@ -351,4 +373,12 @@ check_fake_numbers(PhoneNumber) -> case ?CHECK_FAKE_NUMBERS of true -> kvs:get('FakeNumbers', PhoneNumber); _ -> {error, []} - end. \ No newline at end of file + end. + +control_ver(A,Ver)-> + case A#'Auth'.type of + [] -> A#'Auth'{type = {ver,Ver}}; + verified -> A#'Auth'{type = {reg,Ver}}; + _-> A + end +. \ No newline at end of file diff --git a/apps/roster/src/protocol/roster_bpe.erl b/apps/roster/src/protocol/roster_bpe.erl index f478ede64fd6f6b5f572c6978692feb9209c2b2e..18819011cb1cbcce64d4327763d8c41b5f808986 100644 --- a/apps/roster/src/protocol/roster_bpe.erl +++ b/apps/roster/src/protocol/roster_bpe.erl @@ -1,7 +1,7 @@ -module(roster_bpe). --include("roster.hrl"). --include_lib("kvs/include/feed.hrl"). --include_lib("kvs/include/kvs.hrl"). +-include_lib("roster.hrl"). +%%-include_lib("kvs/include/feed.hrl"). +%%-include_lib("kvs/include/kvs.hrl"). -include_lib("emqttd/include/emqttd.hrl"). -include_lib("n2o/include/n2o.hrl"). -compile({parse_transform, bert_javascript}). @@ -61,22 +61,36 @@ info(#'History'{roster_id = Roster, feed = Feed, size = N, entity_id = MId, stat NewP = length(Js), Data#'History'{data = Js, size = NewP}; _ when Writer == 0 -> Data#'History'{data = []}; - _ -> #io{code = #error{code = invalid_data}} + _ -> roster_channel_helper:error_response_400(Data) +%% #io{code = #error{code = invalid_data}} end, {reply, {bert, D}, Req, State}; -info(#'Job'{id = [], feed_id = {act, <<"publish">>, PhoneId} = Feed, time = [], data = [#'Message'{} | _] = Msgs, status = init} = J0, R, +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]), %J = J0#'Job'{id = Id = kvs:next_id('Job', 1)}, Replay = case roster:phone_id(ClientId) of - PhoneId -> %[roster:send_event(C,<<>>,<<>>,Msg) || Msg=#'Message'{} <- Msgs], + PhoneId -> %roster:restart_module(Pid,?MODULE), n2o_async:pid(system, ?MODULE) ! {send, Msgs}, <<>>; - _ -> #io{code = #error{code = invalid_data}} + _ -> roster_channel_helper:error_response_400(RequestData) +%% #io{code = #error{code = invalid_data}} end, {reply, {bert, Replay}, R, S}; -info(#'Job'{status = init, id = [], feed_id = {act, <<"publish">>, PhoneId} = Feed, time = Time, data = [#'Message'{} | _] = D0} = J0, R, +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]), + %J = J0#'Job'{id = Id = kvs:next_id('Job', 1)}, + Replay = case roster:phone_id(ClientId) of + PhoneId ->[ action(A, R, S) || A <-Actions]; + _ -> roster_channel_helper:error_response_400(RequestData) +%% #io{code = #error{code = invalid_data}} + end, + {reply, {bert, Replay}, R, S}; + + +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) -> %J = J0#'Job'{id = Id = kvs:next_id('Job', 1)}, Current = roster:now_msec(), @@ -96,11 +110,12 @@ info(#'Job'{status = init, id = [], feed_id = {act, <<"publish">>, PhoneId} = Fe {J0#'Job'{time = round(Time / ?QUANT) * ?QUANT}, {0, 0}} end, catch n2o_async:pid(system, ?MODULE) ! {update, J#'Job'{container = chain, data = D, status = pending}, Context, S}, <<>>; - _ -> #io{code = #error{code = invalid_data}} + _ -> roster_channel_helper:error_response_400(J0) +%% #io{code = #error{code = invalid_data}} end, {reply, {bert, Replay}, R, S}; -info(#'Job'{id = Id, time = T0, feed_id = {act, <<"publish">>, PhoneId} = Feed, data = D, settings = Stt, status = update} = M, R, +info(#'Job'{id = Id, time = T0, feed_id = {act, <<"publish">>, PhoneId} = Feed, data = D, settings = Stt, status = update} = RequestData, R, #cx{params = ClientId, client_pid = C} = S) -> roster:info(?MODULE, "UPDATE Time:~p", [roster:msToUT(T0)]), Current = roster:now_msec(), @@ -122,11 +137,14 @@ info(#'Job'{id = Id, time = T0, feed_id = {act, <<"publish">>, PhoneId} = Feed, roster:send_ses(C, roster:phone(PhoneId), NewJ#'Job'{status = update}), <<>>; _ when NData /= 0 -> n2o_async:pid(system, ?MODULE) ! {update, NewJ, {OldTime, 0}, S}, <<>>; - __________ -> #io{code = #error{code = invalid_data}} + __________ -> roster_channel_helper:error_response_400(RequestData) +%% #io{code = #error{code = invalid_data}} end; - _ -> #io{code = #error{code = invalid_data}} + _ -> roster_channel_helper:error_response_400(RequestData) +%% #io{code = #error{code = invalid_data}} end; - _ -> #io{code = #error{code = invalid_data}} + _ -> roster_channel_helper:error_response_400(RequestData) +%% #io{code = #error{code = invalid_data}} end, {reply, {bert, Replay}, R, S}; @@ -146,7 +164,7 @@ info(#'Job'{proc = Proc, data = D, time = Time, events = [], status = restart} = {reply, {bert, {io, bpe:amend(Proc, Docs, noflow), <<>>}}, R, S}; -info(#'Job'{id = Id, feed_id = {act, <<"publish">>, UID} = Feed, status = delete} = M, R, #cx{params = ClientId, client_pid = C} = S) -> +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]), PhoneId = roster:phone_id(ClientId), D = case kvs:get('Job', Id) of @@ -158,7 +176,8 @@ info(#'Job'{id = Id, feed_id = {act, <<"publish">>, UID} = Feed, status = delete _ -> skip end, kvs:put(Job), roster:send_ses(C, roster:phone(PhoneId), Job), <<>>; - _ -> #io{code = #error{code = invalid_data}} + _ -> roster_channel_helper:error_response_400(RequestData) +%% #io{code = #error{code = invalid_data}} end, %timer:apply_after(Time, bpe, cleanup, [Id]), {reply, {bert, D}, R, S}; @@ -175,9 +194,11 @@ info(#'Job'{id = Proc, status = complete} = M, R, S) -> roster:info(?MODULE, "complete:~w", [M]), {reply, {bert, {io, bpe:complete(Proc), <<>>}}, R, S}; -info(#'Job'{feed_id = Feed} = M, R, S) -> +info(#'Job'{feed_id = Feed} = RequestData, R, S) -> roster:info(?MODULE, "~p::Job/unknown", [Feed]), - {reply, {bert, #io{code = #error{code = invalid_data}}}, R, S}; + {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}. @@ -192,28 +213,30 @@ proc(init, #handler{name = roster_bpe} = Async) -> %% [H|T]=lists:reverse(ProcIDs), % % P1=job_process:update_event(P, 'Action', {0, {0, 0, 1}}), - P1 = job_process:update_opt(bpe:load(Id = Top), {send_pid, C}), try case bpe:find_pid(Top) of Pid when is_pid(Pid) -> case process_info(Pid) of - undefined -> skip; -% _ when P1#process.task /= 'Timeout' -> bpe:complete(Top); - _ -> bpe:complete(Top) - end; + undefined -> restart_bpe(bpe:load(Top), C); + _ -> Top + end, + timer:sleep(300), + bpe:complete(Top); _ -> skip end catch _:_ -> skip end, Top; _ -> - {ok, Id0} = bpe:start(job:def(#'Schedule'{id = {0, {0, 0, 1}}, proc = <<"publish">>}), [{send_pid, C}]), + {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]), % Proc is transfered throuw 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 -> + 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, @@ -271,10 +294,11 @@ proc({update, #'Job'{id = Id0, time = Time, feed_id = {act, <<"publish">>, Phone 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]), - {reply, [], H#handler{state = {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]), + 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 @@ -343,9 +367,10 @@ proc({clean, #'process'{id = Id} = P}, #handler{state = {C, Proc}} = H) -> proc({send, Msgs}, #handler{state = {C, Proc}} = H) -> roster:info(?MODULE, "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(50) end || Msg = #'Message'{} <- Msgs], - {reply, [], H}; + [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}}; @@ -354,19 +379,19 @@ proc({mqttc, _C, disconnected}, State) -> {ok, State}. publish(Proc) -> n2o_async:pid(system, ?MODULE) ! {publish, bpe:load(Proc)}. -restart_bpe(#process{id = Id} = P, C) -> +restart_bpe(#process{id = Id} = P, _C) -> try case bpe:find_pid(Id) of Pid when is_pid(Pid) -> case process_info(Pid) of undefined ->%Pid ! {'DOWN', <<>>, <<>>, <<>>, <<>>}, [supervisor:F(bpe_sup, Id) || F <- [terminate_child, delete_child]], bpe:cache({process, Id}, undefined), - {ok, Pi} = bpe:start(P, [{send_pid, C}]), Pi; - _ -> Id + {ok, Pi} = bpe:start(P, []), Pi; + _ -> Id % job_process:update_opt(P,{send_pid, C}) end; - _ -> {ok, Pi} = bpe:start(P, [{send_pid, C}]), Pi + _ -> {ok, Pi} = bpe:start(P, []), Pi end catch - Err:Rea -> {ok, ID} = bpe:start(P, [{send_pid, C}]), ID + Err:Rea -> {ok, ID} = bpe:start(P, []), ID end. amend(ProcId, Form, []) -> bpe:amend(ProcId, Form); @@ -384,3 +409,6 @@ set_schedule(_, Time, #'Job'{id = Id, proc = Proc, time = T0} = J) -> % end. + +action(#'History'{status=update}=Term,R,S)-> + roster_history:info(Term, R, S). \ No newline at end of file diff --git a/apps/roster/src/protocol/roster_favorite.erl b/apps/roster/src/protocol/roster_favorite.erl index 702553201afc5ed1f7e1875e9cba0fd7f8464de0..62118df149bf8061c1230b47ba66bca74ff5d4cb 100644 --- a/apps/roster/src/protocol/roster_favorite.erl +++ b/apps/roster/src/protocol/roster_favorite.erl @@ -15,45 +15,38 @@ info(#'Tag'{status = edit} = Tag, Req, #cx{params = ClientId} = State) -> roster:info(?MODULE, "~p:Tag/edit:~p", [ClientId, Tag]), {reply, {bert, Tag}, Req, State}; -info(#'ExtendedStar'{}, Req, #cx{params = ClientId} = 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]), - Response = case kvs:get('Roster', RosterId) of - {ok, #'Roster'{favorite = FavMsgs}} -> - roster:get_extended_star(PhoneId, FavMsgs); - false -> #io{code = #error{code = roster_not_found}} - end, - roster:info(?MODULE, "Debug.Response:~p", [Response]), - {reply, {bert, Response}, Req, State}; + IO = case kvs:get('Roster', RosterId) of + #error{} = Err -> #io{code = Err}; + {ok, #'Roster'{favorite = Favs} = Roster} -> + [roster:send_action(C, ClientId, ExStars) + || ExStars <- roster:split_objlist(Favs, Roster, 0, [], [])], <<>> + end, + {reply, {bert, IO}, Req, State}; -info(#'Star'{status = add, message = Msg} = Data, Req, #cx{params = ClientId, client_pid = C} = 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]), - RosterId = roster:roster_id(roster:phone_id(ClientId)), - Response = case kvs:get('Roster', RosterId) of + RosterId = roster:roster_id(PhoneId = roster:phone_id(ClientId)), + IO = case kvs:get('Roster', RosterId) of {ok, #'Roster'{favorite = StFavMsgs} = Roster} -> - case is_record(Msg, 'Message') of - true -> #'Message'{id = MsgId} = Msg, - case lists:keyfind(MsgId, #'Message'.id, - lists:flatten([M || #'Star'{message = M} <- StFavMsgs])) of - false -> UpdMsg = Msg#'Message'{seenby = [], repliedby = [], status = []}, - UpdData = Data#'Star'{roster_id = RosterId, message = UpdMsg, id = kvs:next_id('Star', 1)}, - UpdFavMsg = lists:keystore(Msg, #'Star'.message, StFavMsgs, UpdData), - UpdRoster = Roster#'Roster'{favorite = UpdFavMsg, update = roster:now_msec(), status = update}, - kvs:put(UpdRoster), - roster:send_ses(C, roster:phone(roster:phone_id(ClientId)), UpdData); - _ -> skip - end, - <<>>; - false -> #io{code = #error{code = invalid_data}} + case lists:keyfind(MsgId, #'Message'.id, + lists:flatten([M || #'Star'{message = M} <- StFavMsgs])) of + false -> UpdMsg = Msg#'Message'{seenby = [], repliedby = [], status = []}, + UpdData = Data#'Star'{roster_id = RosterId, message = UpdMsg, id = kvs:next_id('Star', 1)}, + UpdFavMsg = lists:keystore(Msg, #'Star'.message, StFavMsgs, UpdData), + kvs:put(Roster#'Roster'{favorite = UpdFavMsg, update = roster:now_msec(), status = update}), + roster:send_ses(C, roster:phone(PhoneId), UpdData), <<>>; + _ -> <<>> end; #error{} -> #io{code = #error{code = roster_not_found}} end, - roster:info(?MODULE, "Debug.Response:~p", [Response]), - {reply, {bert, Response}, Req, State}; + {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]), - Response = case kvs:get('Roster', roster:roster_id(roster:phone_id(ClientId))) of + Response = case kvs:get('Roster', roster:roster_id(ClientId)) of {ok, #'Roster'{favorite = StFavMsgs} = Roster} -> case lists:keyfind(MsgId, #'Star'.id, StFavMsgs) of #'Star'{} = StarToBeRemoved -> diff --git a/apps/roster/src/protocol/roster_friend.erl b/apps/roster/src/protocol/roster_friend.erl index ba0998bf973b87fb06e9c6c4c84f48b2536a9401..f01b71f83349cc1b212ac3a5af6a32290d1acc62 100644 --- a/apps/roster/src/protocol/roster_friend.erl +++ b/apps/roster/src/protocol/roster_friend.erl @@ -64,15 +64,16 @@ info(#'Friend'{friend_id = Friend, status = confirm}, Req, #cx{params = ClientId {error, not_found} -> kvs_stream:save(#writer{id = Feed}), [NewR1, NewR2] = [(kvs_stream:save(kvs_stream:reader(Feed)))#reader.id || _ <- [1, 1]], #writer{cache = Msg} = roster:add_message(#'Message'{feed_id = Feed, from = Me, - to = Friend, status = [], type = [sys], files = [#'Desc'{payload = <<>>}]}), + msg_id = roster:generate_server_id(), to = Friend, status = [], + type = [sys], files = [#'Desc'{payload = <<>>}]}), {Mc#'Contact'{reader = NewR1, last_msg = Msg}, Fc#'Contact'{reader = NewR2, last_msg = Msg}}; _ -> {roster:get_contact(ToR, Me), roster:get_contact(FromR, Friend)} end, 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:Sub(Client, roster:Topic(Me)) || Client <- Tos, {Topic, Sub} <- TSs], - [n2o:Sub(Client, roster:Topic(Friend)) || Client <- Froms, {Topic, Sub} <- TSs], - [n2o:subscribe(Client, Topic) || Client <- Tos ++ Froms], + [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: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)})), n2o_async:pid(system, ?MODULE) ! {send_push, Mec2, Friend, <<"friend">>}, @@ -111,11 +112,11 @@ info(#'Friend'{friend_id = Friend, status = Status}, Req, Remote = roster:update_contact(ToR, MeC), Local = roster:update_contact(FromR, FriendC), [AcFrT, MucFrT] = [roster:T(Friend) || T <- [ac_topic, muc_topic]], - [n2o:Unsub(Client, AcFrT) || Client <- Froms], - [n2o:Unsub(Client, roster:p2p_topic(Me, Friend)) || Client <- Froms], + [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 - true -> [n2o:Sub(Client, MucFrT) || Client <- Froms]; + true -> [n2o_vnode:Sub(Client, MucFrT) || Client <- Froms]; _ -> skip end, 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)})), diff --git a/apps/roster/src/protocol/roster_history.erl b/apps/roster/src/protocol/roster_history.erl index ee3847d71eed5f3836eb5b930263f30ca5246fd3..8afdf1bf4f3cff5ca608ce009f3809db12bbf0c8 100644 --- a/apps/roster/src/protocol/roster_history.erl +++ b/apps/roster/src/protocol/roster_history.erl @@ -28,12 +28,30 @@ info(#'History'{status = get_reply, entity_id = Id} = History, Req, [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, + #cx{} = State) when N > 0, MId > 0 -> + Msgs2 = lists:flatten( + [begin {reply, {bert, #'History'{data = Msgs}}, _, _} + = info(History#'History'{status = get, size = Size}, Req, State), Msgs + end || Size <- [N, -N]]), + Msgs3 = lists:keydelete(MId, #'Message'.id, Msgs2), + {reply, {bert, History#'History'{data = Msgs3, size = length(Msgs3)}}, Req, State}; +info(#'History'{status = Status, data = Msgs} = History, Req, #cx{state = []} = State) + when Status == image; Status == video; Status == file; Status == link; Status == audio; + Status == contact; Status == location; Status == get_lost; Status == text -> + info(History#'History'{status = get, + data = [Msg#'Message'{files = [#'Desc'{mime = atom_to_binary(Status, latin1)}]} || #'Message'{} = Msg<-Msgs]}, + Req, State#cx{state = {status, Status}}); +info(#'History'{status = get} = History, Req, #cx{state = []} = State) -> + info(History, Req, State#cx{state = {status, get}}); info(#'History'{status = get, feed = [], data = [#'Message'{feed_id = Feed}|_]} = History, Req, State) when Feed /= []-> info(History#'History'{feed = Feed}, Req, State); info(#'History'{status = get, roster_id = Roster0, feed = Feed, size = N, entity_id = MId, data = MsgData} = History, Req, - #cx{params = ClientId} = State) when N /= 0 andalso (is_record(Feed, p2p) orelse is_record(Feed, muc)) -> + #cx{params = ClientId, state = {status, InitialStatus}} = State) + 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", [PhoneId, ClientId, Feed]), + roster:info(?MODULE, "~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, @@ -51,18 +69,25 @@ info(#'History'{status = get, roster_id = Roster0, feed = Feed, size = N, entity {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 = kvs_stream:load_reader(R), - MId2 = case MId of 0 -> LastMsgId; _ -> MId end, + 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; + 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 = case MsgData of - [#'Message'{files = [#'Desc'{mime = Mime = <<_/integer, _/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; - [] -> fun roster:Filter/2; - _ -> fun roster:msg_filter/2 end, + {InnerFilterFun, Mime2} = case MsgData of + [#'Message'{files = [#'Desc'{mime = Mime = <<_/integer, _/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, StartId = case Reader of #reader{cache = {'Message', ReadMsg}} @@ -83,7 +108,22 @@ info(#'History'{status = get, roster_id = Roster0, feed = Feed, size = N, entity (_, Acc) -> Acc end, {Msgs, _} = roster:fold(FilterFun, {[], roster:start_reader(Reader)}, 'Message', StartId, FId, #kvs{mod = store_mnesia}, Iter, StopFun), - History#'History'{data = Msgs, size = length(Msgs)} + + Msgs2 = case is_integer(MId) of + true when MId > 0, Mime2 == [] -> + {ok, #'Message'{} = EntityMsg} = kvs:get('Message', MId), + lists:reverse(lists:ukeymerge(#'Message'.id, lists:reverse(Msgs), + [EntityMsg#'Message'{files = []}])); + _ -> Msgs + end, + ResponseMsgs = case MId of + 0 -> +%% nullify Message.next for last message in query + LastMsgObject = lists:last(Msgs2), + lists:sublist(Msgs2, length(Msgs2) -1) ++ [LastMsgObject#'Message'{next = []}]; + _ -> Msgs2 end, + CheckedResponseMsgs = [roster:check_message(Msg) || Msg <- ResponseMsgs], + History#'History'{data = ResponseMsgs, size = length(ResponseMsgs), status = InitialStatus} end, {reply, {bert, IO}, Req, State}; @@ -156,7 +196,7 @@ info(#'History'{feed = Feed, status = delete}, Req, #cx{client_pid = C, params = #error{} = Res -> #io{code = Res}; {_Unread, Internal} -> %% D=case is_integer(Unread) of true -> roster:send_ses(C, From, roster:update_field(FList,Record,unread,Unread)), <<>>; false -> Unread end, - roster:send_feed(C, Feed, roster:readmsgs(Internal, PhoneId)), + roster:send_feed(C, Feed, roster:readmsgs(Internal#'Message'{next = []}, PhoneId)), %% send push if no history clean error occured n2o_async:pid(system, ?MODULE) ! {send_push, PhoneId, Feed, ?HISTORY_DELETE_ACTION}, <<>> end, @@ -181,6 +221,7 @@ clean_history(#p2p{} = Feed, PhoneId) -> clean_history(Feed, PhoneId, Friend, [roster:get_contact(PhoneId, Friend)]). 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:generate_server_id()]), files = [#'Desc'{payload = <<"History was removed">>}]}), case kvs:get(writer, Feed) of {ok, #writer{count = Top, id = #muc{name = MUC}} = W} -> diff --git a/apps/roster/src/protocol/roster_link.erl b/apps/roster/src/protocol/roster_link.erl new file mode 100644 index 0000000000000000000000000000000000000000..0ba31b4ebdd0bfa31cffa7088f6a8165defa79dc --- /dev/null +++ b/apps/roster/src/protocol/roster_link.erl @@ -0,0 +1,126 @@ +-module(roster_link). +-include("../../include/roster.hrl"). +-include_lib("roster/include/static/roster_text.hrl"). +-include_lib("roster/include/static/roster_var.hrl"). +-include_lib("roster/include/static/main_text.hrl"). +-include_lib("n2o/include/n2o.hrl"). +-compile(export_all). + +info(#'Link'{id = LinkId, type = group, status = join = LStatus} = RequestData, + Req, #cx{params = ClientId} = State) when LinkId /= [] -> + roster:info(?MODULE, "~p:Link/~p:~p", [ClientId, LStatus, RequestData]), +Res = case get_room_by_link_Id(LinkId) of + #ok{code = #'Room'{id = RoomId} = Room}-> + #'Member'{id = MemberId, reader = MemberReader} = + Member = case roster:muc_member(ClientId, RoomId) of + #'Member'{} = M -> M; + _ -> + PhoneID = roster:phone_id(ClientId), + Member0 = #'Member'{ + feed_id = #muc{name = RoomId}, + phone_id = PhoneID, + reader = 0, update = 0, + presence = roster:is_online(PhoneID), + status = member}, + #ok{code = Member1} = roster:to_member(PhoneID), + roster:patch_member(Member0, Member1) + end, + Messages = case roster:member_lastmsg(Member) of + {error, _} -> []; + {_, LM} -> LM + end, + {UnreadMsg, LastMsg, _} = roster:unread_msg({'Message', Messages}, MemberReader, MemberId), + {#'Member'{}, [], #'Room'{} = Room1} = + roster:add_member(Room, Member, {no_muc_message, Room#'Room'.readers}), + {As, Ms} = roster:split_members(roster:members(#muc{name = Room1#'Room'.id}, presence)), + roster:reader_cache(Room1#'Room'{status = join, last_msg = LastMsg, unread = UnreadMsg, members = Ms, admins = As}); + #error{code = not_found} = Err -> Err + end, + roster:info(?MODULE, "Link/~p.Response:~p", [Res, LStatus]), + {reply, {bert, Res}, Req, State}; + +info(#'Link'{id = LinkId, name = RoomId, type = group, status = update = LStatus} = RequestData, + Req, #cx{params = ClientId} = State) when RoomId /= [], LinkId /= [] -> + roster:info(?MODULE, "~p:Link/~p:~p", [ClientId, LStatus, RequestData]), + Res = case roster:muc_member(ClientId, RoomId) of + #'Member'{status = admin} = M-> + update_link(RoomId, RequestData); + #'Member'{status = member} -> + error_code(permission_denied); + _-> + error_code(invalide_data) + end, + roster:info(?MODULE, "Link/~p.Response:~p", [Res, LStatus]), + {reply, {bert, Res}, Req, State}; + +info(#'Link'{id = LinkId, type = group, status = get = LStatus} = RequestData, + Req, #cx{params = ClientId} = State) when LinkId /= [] -> + roster:info(?MODULE, "~p:Link/~p:~p", [ClientId, LStatus, RequestData]), + Res = case get_room_by_link_Id(LinkId) of + #ok{code = #'Room'{id = RoomId} = Room } -> #ok{code = Room#'Room'{status = info}}; + #error{code = not_found} = Err -> Err + end, + roster:info(?MODULE, "Link/~p.Response:~p", [Res, LStatus]), + {reply, {bert, Res}, Req, State}; + +info(#'Link'{status = delete = LStatus} = RequestData, Req, #cx{params = ClientId} = State) -> + roster:info(?MODULE, "~p:Link/~p:~p", [ClientId, LStatus, RequestData]), + Res = error_code(permission_denied), + roster:info(?MODULE, "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]), + {reply, {bert, io_error_code(invalid_data)}, Req, State}. + + +gen_link(RoomId)-> + TmpLinkId = hash(RoomId), + LinkId = case kvs:get('Link', TmpLinkId) of + #ok{code = #'Link'{id = TmpLinkId}}-> gen_link(RoomId); + #error{code = not_found} -> TmpLinkId + end, + +#'Link'{ + % id = <>, + id = LinkId, + name = RoomId, + type = group + }. + +update_link(RoomId, #'Link'{name = LinkId} = Link) -> + Res = case kvs:get('Room', RoomId) of + #ok{code = #'Room'{links = Links0} = Room} -> + NewLink = gen_link(RoomId), + % Links1 = lists:keyreplace(LinkId, #'Link'.id, Links0, NewLink), + ok = kvs:put(Room#'Room'{links = [NewLink]}), + ok = kvs:delete('Link', LinkId), + ok = kvs:put(NewLink), + NewLink; + #error{code = not_found} = Err -> Err + end, + Res. + +purge_link(RoomId)-> + [kvs:delete('Link', Lid) || #'Link'{id = Lid,name = RoomId}<- kvs:all('Link')], + ok. + +hash(Data) -> + {_, <>} = + roster:gen_token([], Data), Hash. +get_room_by_link_Id(<>)-> + get_room_by_link_Id(LinkId); +get_room_by_link_Id(Id) -> + case kvs:get('Link',Id) of + #ok{code = #'Link'{name = RoomId}}-> + case kvs:get('Room',RoomId) of + #ok{code = #'Room'{}} = Ok -> Ok; + #error{code = not_found} = Err -> Err + end; + #error{code = not_found} = Err -> Err + end. +io_error_code(Payload) -> io_code(error_code(Payload)). +io_ok_code(Payload) -> io_code(ok_code(Payload)). +io_code(Payload) -> #io{code = Payload}. +error_code(Payload) -> #error{code = Payload}. +ok_code(Payload) -> #ok{code =Payload}. diff --git a/apps/roster/src/protocol/roster_message.erl b/apps/roster/src/protocol/roster_message.erl index ad0b095094044dccb3b687d9ad35c358807d14c7..ce20676f846d7177317228299ab8fa4e73b2f48a 100644 --- a/apps/roster/src/protocol/roster_message.erl +++ b/apps/roster/src/protocol/roster_message.erl @@ -2,6 +2,7 @@ -include("roster.hrl"). -include_lib("n2o/include/n2o.hrl"). -include_lib("kvs/include/kvs.hrl"). +-include_lib("roster/include/static/rest_text.hrl"). -compile(export_all). -define(TIMEOUT, {1, {0, 0, 0}}). @@ -16,15 +17,27 @@ info(#'Typing'{phone_id = Phone, comments = Comments}, Req, roster:info(?MODULE, "~p:Typing/:~p", [Phone, Comments]), {reply, {bert, <<>>}, Req, State}; +info(#'Message'{msg_id=MsgID, status=Status} = M, Req, #cx{state=[]}=State) when MsgID /= [], Status/=update-> + roster:info(?MODULE, "~p:~p:Message/:~p", [MsgID, State, Status]), + case kvs:index('Message', msg_id, MsgID) of + [#'Message'{}=SM] -> {reply, {bert, #'Ack'{id=MsgID}}, Req, State}; + [ ] -> info(M, Req, State#cx{state=ack}); + E -> {reply, {bert, E}, Req, State} + end; +info(#'Message'{type = T} = M, Req, #cx{state=[]}=State)-> + info(M#'Message'{type = T}, Req, State#cx{state=ack}); info(#'Message'{feed_id = #muc{name = To}, to = []} = RequestData, Req, State) when To /= []-> - info(RequestData#'Message'{to = To}, Req, State); -info(#'Message'{status = [], id = [], feed_id = F, from=From0, to = To, created = Created, + info(RequestData#'Message'{to = To}, Req, #cx{state=ack}=State); +info(#'Message'{status = [], id = [], feed_id = F, from=From0, to = To, created = [], type = Type, files = [#'Desc'{payload = Payload} | _] = Descs} = Msg, - Req, #cx{client_pid = C, params = ClientId} = State) - when is_list(Type) andalso (Type == [] orelse (is_atom(hd(Type)) andalso hd(Type) /= sys)) -> - From = roster:phone_id(ClientId, From0), + Req, #cx{client_pid = C, params = ClientId, state=ack} = State) -> + MSG_LTNCY = os:system_time(), + From = case hd(binary:split(ClientId, <<"_">>)) of + <<"sys">> -> From0; + <<"emqttd">> -> roster:phone_id(ClientId) + end, + Created=roster:now_msec(), FId = roster:roster_id(From), - roster:verify_descs(Descs), FeedData = {R, UID} = case Feed = roster:feed_key(F) of _ when From == <<>> -> #error{code = permission_denied}; @@ -42,47 +55,63 @@ info(#'Message'{status = [], id = [], feed_id = F, from=From0, to = To, created end; _E -> #error{code = permission_denied} end, - IO = case R of error -> #io{code = FeedData}; - _ -> - case roster:link_msg(Msg, UID) of - #error{} = Err -> #io{code = Err}; - LnkRes -> - % create message amd save to db - Writer = #writer{cache = #'Message'{id = MsgId} = M} = - kvs_stream:save(kvs_stream:add((kvs_stream:load_writer(Feed))#writer{args = Msg#'Message'{container = chain, - feed_id = Feed, created = case Created of [] -> roster:now_msec(); _ -> Created end}})), - - case Feed of - #muc{name = Room} -> - {ok, Room2} = kvs:get('Room', Room), - kvs:put(Room2#'Room'{last_msg = MsgId}); - _ -> skip end, + IO = + case R of + error -> #io{code = FeedData}; + _ -> + case roster:link_msg(Msg, UID) of + #error{} = Err -> #io{code = Err}; + 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 = case Created of [] -> roster:now_msec(); _ -> Created end}})), + 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'{id=RID, prev=Prev, repliedby = ReplBy}=MLink -> - NewM = case Prev of [] -> {ok, NewMsg} = kvs:get('Message', RID), NewMsg; _ -> MLink end, - kvs:put(LnkRes2 = NewM#'Message'{repliedby = [MsgId | ReplBy]}), - LnkRes2; - _ -> LnkRes end, - roster:send_feed(C, F, Msg2 = M#'Message'{link = LnkRes3}), - roster:info(?MODULE, "~p:~p:Message/new:~P", [From, To, Payload, 100]), - n2o_async:pid(system, ?MODULE) ! {send_push, From, To, Msg2, []}, - <<>> - end - 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:~p:Message/new:~P", [From, To, Payload, 100]), +%% 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 + end, + <<>> + end + end, + prometheus_histogram:observe(roster_msg_latency, [new], roster:osd(MSG_LTNCY)), {reply, {bert, IO}, Req, State}; -info(#'Message'{status = edit, id = Id, feed_id = Feed, from = From, to = To, mentioned = Mentioned, - files = [#'Desc'{payload = Payload} | _] = Descs} = RequestData, Req, - #cx{params = ClientId, client_pid = C} = State) - when is_integer(Id) andalso is_list(Mentioned) andalso ([] == Mentioned orelse is_integer(hd(Mentioned))) -> - PhoneId = roster:phone_id(ClientId, From), +info(#'Message'{status = edit, id = Id, msg_id = ClMID, feed_id = Feed, from = From, to = To, mentioned = Mentioned, + files = [#'Desc'{payload = Payload} | _] = Descs}, Req, + #cx{params = ClientId, client_pid = C, state=ack} = State) -> + MSG_LTNCY = os:system_time(), + PhoneId = roster:phone_id(ClientId, From), roster:info(?MODULE, "~p:~p:Message/edit:~P", [PhoneId, To, Payload, 100]), DV = length([D || D = #'Desc'{id = ID} <- Descs, is_binary(ID), ID /= <<>>]) == length(Descs), @@ -102,33 +131,33 @@ info(#'Message'{status = edit, id = Id, feed_id = Feed, from = From, to = To, me {ok, _} -> #io{code = #error{code = permission_denied}}; #error{} -> #io{code = #error{code = message_not_found}} end, - IO = case Data of - #io{} -> Data; - {#'Message'{to = To1, type = Type, prev = Prev} = Message, Reader, Ent} -> - % create system message - Smsg = roster:desc_id(#'Message'{status = edit, 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 = Internal} = kvs_stream:save(kvs_stream:add((kvs_stream:load_writer(Feed))#writer{args = Smsg})), - M = case Prev of [] -> {ok, NewMsg} = kvs:get('Message', Id), NewMsg; - %% TODO remove kvs:get (iterator from mabe obtained from Internal messsage) - _ -> Message end, - 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), - n2o_async:pid(system, ?MODULE) ! {send_push, From, To, Internal, ?MSG_EDIT_ACTION}, - % publish - roster:send_feed(C, Feed, Internal), <<>>; - E -> E - end, - {reply, {bert, IO}, Req, State};%#cx{from = roster:ses_topic(PhoneFrom)}}; + IO = + case Data of + #io{} -> Data; + {#'Message'{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})), + 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), + n2o_async:pid(system, ?MODULE) ! {send_push, From, To, Internal, ?MSG_EDIT_ACTION}, +%% publish + roster:send_feed(C, Feed, Internal), <<>>; + E -> E + end, + prometheus_histogram:observe(roster_msg_latency, [edit], roster:osd(MSG_LTNCY)), + {reply, {bert, IO}, Req, State}; -info(#'Message'{id = Id, feed_id = Feed, from = From0, seenby = Seen, status = delete}, Req, - #cx{params = ClientId, client_pid = C} = State) when is_integer(Id) -> +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]), PhoneId = roster:phone_id(ClientId, From0), D = case kvs:get('Message', Id) of @@ -176,49 +205,50 @@ info(#'Message'{id = Id, feed_id = Feed, from = From0, seenby = Seen, status = d {Msg, roster:ses_topic(roster:phone(PhoneId)), Rdr, Cont, Seen ++ Seen0}; _ -> #io{code = #error{code = invalid_data}} end; _ -> #io{code = #error{code = invalid_data}} end, - {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, feed_id = Feed, from = From1, to = To1, link = MsgId, seenby = NewSeen, - files = [#'Desc'{payload = <<"Delete">>}], created = roster:now_msec()}), - #writer{cache = Internal} = kvs_stream:save(kvs_stream:add((kvs_stream:load_writer(Feed))#writer{args = Smsg})), - M = case Prev of [] -> {ok, NewMsg} = kvs:get('Message', MsgId), NewMsg; _ -> Message end, - N = M#'Message'{seenby = NewSeen}, - kvs:put(N), - % move cursor - kvs_stream:save(R = kvs_stream:top(kvs_stream:load_reader(Reader))), - roster:put_readers(write_top, Ent, 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 - {ok, #'Room'{last_msg = Id} = RoomRec} -> - #'Message'{id = LastSeenMsgId} = roster:while(status_msg, R, 0, #'Message'.status), - kvs:put(RoomRec#'Room'{last_msg = LastSeenMsgId}); - _ -> skip end; _ -> skip end, - n2o_vnode:send(C, Topic, term_to_binary(Internal)), + {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 + %% 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 + {ok, #'Room'{last_msg = Id} = RoomRec} -> + #'Message'{id = LastSeenMsgId} = roster:while(status_msg, R, 0, #'Message'.status), + kvs:put(RoomRec#'Room'{last_msg = LastSeenMsgId}); + _ -> skip end; _ -> skip end, + n2o_vnode:send(C, Topic, term_to_binary(Internal)), %% NOTE! send push about deleted msg only if acted user is in the seenby - case Seen of [-1] -> n2o_async:pid(system, ?MODULE) ! {send_push, From0, To1, Internal, ?MSG_DELETE_ACTION}; _ -> skip end, - 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, - "link ~p for reply not found in ~p message", [Link, Message#'Message'.id]), - skip; - {ok, #'Message'{feed_id = Feed, repliedby = ReplBy} = LnkMessage} -> - kvs:put(LnkMessage2 = LnkMessage#'Message'{repliedby = lists:delete(MsgId, ReplBy)}), - roster:send_feed(C, Feed, LnkMessage2), - skip; - _ -> skip end; - _ -> skip end, - <<>>; - E -> E - end}, Req, State}; + case Seen of [-1] -> + n2o_async:pid(system, ?MODULE) ! {send_push, From0, To1, Internal, ?MSG_DELETE_ACTION}; _ -> + skip end, + 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, + "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)}), + roster:send_feed(C, Feed, LnkMessage2), <<>>; + _ -> <<>> end; + _ -> <<>> end; + E -> E + end}, Req, State}; info(#'Message'{status = update, id = Id, feed_id = Feed, from = From, to = To, files = [#'Desc'{id = ID, payload = Payload, data = Data, mime = DMime} = ND | _] = Files}, Req, - #cx{params = ClientId, client_pid = C} = State) when is_integer(Id) -> + #cx{params = ClientId, client_pid = C, state=ack} = State) when is_integer(Id) -> + MSG_LTNCY = os:system_time(), PhoneId = case ClientId of <<"sys_bpe">> -> From; <<"emqttd_", _/binary>> -> roster:phone_id(ClientId) end, roster:info(?MODULE, "~p:~p:Message/update:~P", [PhoneId, To, Payload, 100]), Lang = roster:get_data_val(?LANG_KEY, Data), @@ -264,10 +294,11 @@ info(#'Message'{status = update, id = Id, feed_id = Feed, from = From, to = To, _ -> #io{code = #error{code = invalid_data}} end; _ -> #io{code = #error{code = message_not_found}} end, + prometheus_histogram:observe(roster_msg_latency, [update], roster:osd(MSG_LTNCY)), {reply, {bert, IO}, Req, State}; -info(#'Message'{from = From, to = To}, Req, State) -> - roster:info(?MODULE, "~p:~p:Message/unknown", [From, To]), +info(#'Message'{from = From, to = To} = ReqData, Req, State) -> + roster:info(?MODULE, "~p:~p:Message/unknown:~p", [From, To, ReqData]), {reply, {bert, #io{code = #error{code = invalid_data}}}, Req, State}. proc(init, #handler{name = roster_message} = Async) -> diff --git a/apps/roster/src/protocol/roster_presence.erl b/apps/roster/src/protocol/roster_presence.erl index 39a99c7c681272c38f362ab11506b526fc8d519f..1c3c60048e786fb5cb1ec2bf2832537b4a58b8e4 100644 --- a/apps/roster/src/protocol/roster_presence.erl +++ b/apps/roster/src/protocol/roster_presence.erl @@ -1,16 +1,30 @@ -module(roster_presence). -include("roster.hrl"). -include_lib("n2o/include/n2o.hrl"). +-include_lib("rest_static.hrl"). -compile(export_all). -on_connect(Phone, ClientId, C) -> - roster:info(?MODULE, "~p:~p:CONNECT", [Phone, ClientId]), +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}. + + +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]), + 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]), roster:delete_sessions(Phone); - #'Profile'{rosters = Rosters} = P -> - roster:send_action(C, ClientId, (P#'Profile'{rosters = [roster:roster(I) || I <- Rosters]})#'Profile'{status = init}) + #'Profile'{} = P when Ver==?VERSION -> ok; + #'Profile'{} = P-> + roster:send_profile(P#'Profile'{settings = amazon_settings(P), status = init}, [], 0, ClientId, C) end catch Err:Rea -> n2o:error(?MODULE, "Catch:~p~n", [n2o:stack_trace(Err, Rea)]) @@ -44,4 +58,25 @@ send_presence(Status, Phone, C, ClientId) when Status == online; Status == offli #error{} -> skip end || AccId <- Rosters], P2; _ -> P end; - _ -> {error, profile_not_found} end. \ No newline at end of file + _ -> {error, profile_not_found} end. + + +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()}), + [case kvs:get('Roster', AccId) of + {ok, #'Roster'{phone = Phone}} -> + PId = roster:phone_id(Phone, AccId), + [roster:Send(C, PhoneId, #'Presence'{uid = PId, + status = Status}) || Send <- [send_muc, send_ac]]; + #error{} -> skip + end || AccId <- Rosters], #'Presence'{uid = PhoneId, status = Status}; + _ -> {error, profile_not_found} end. + +%% AWS keys for android build verification by Google Play Market +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 diff --git a/apps/roster/src/protocol/roster_profile.erl b/apps/roster/src/protocol/roster_profile.erl index f488ba94fa9a29cdfd1d90c53404acdcea42e100..4cad2101db83b0f69bf3a2969e7af231491726e4 100644 --- a/apps/roster/src/protocol/roster_profile.erl +++ b/apps/roster/src/protocol/roster_profile.erl @@ -24,6 +24,28 @@ info(#'Profile'{status=patch, phone=Phone, balance = Balance} = Data, Req, {error, _} = E -> #io{code = E} end, {reply, {bert, Res}, Req, State}; +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])]), + case kvs:get('Profile',UID) of + {ok,#'Profile'{rosters=Rosters}=P}-> + NewRosters=(Rosters--Rs), + case Rosters--NewRosters of + [] -> {reply, {bert, {io, {error, roster_not_found}, <<>>}}}; + List -> Deleted=lists:flatten([begin PId=roster:phone_id(UID,X), + [begin [UIDF, FId]=roster:parts_phone_id(Friend), + roster:update_contact(FId,Cont=#'Contact'{phone_id = PId, reader = Rd, nick= <<"Deleted user">>, status=deleted}), + roster:send_ses(C, UIDF, Cont) end + || #'Contact'{phone_id = Friend, reader = Rd} <- roster:get_contacts(X), Friend =/=PId], + roster:send_ses(C, UID, #'Roster'{id = X, status=del}), + del_roster(UID,X) end + || X<- List ]), + % TODO kvs:put if Profile will not have information about deleted accounts + NewP=P#'Profile'{rosters=Rosters--Deleted}, + {reply, {bert, NewP}, Req, State} end; + {error, R} -> {reply, {bert, {io, {error, R}, <<>>}}, Req, State} end; + + 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])]), @@ -34,61 +56,24 @@ info(#'Profile'{status = remove, phone = Phone}=Data, Req, || #'Contact'{phone_id = Friend} <- roster:get_contacts(X), Friend =/=PhoneId] end || X<- Rosters ], roster:send_ses(C, Phone, P = #'Profile'{phone=Phone, status=remove}), + timer:sleep(10), %% TODO in future update n2o:unsubscribe roster:purge_user(Phone), {reply, {bert, P}, Req, State}; {error, R} -> {reply, {bert, {io, {error, R}, <<>>}}, Req, State} end; -info(#'Profile'{status = get}, Req, #cx{params = ClientId} = State) -> +info(#'Profile'{status = get, update = LastSync, settings = Settings}, Req, #cx{params = ClientId, client_pid = C} = State) -> Phone = roster:phone(ClientId), roster:info(?MODULE, "~p:Profile/get.Request:~p", [ClientId, Phone]), R = case kvs:get('Profile', Phone) of - {ok, #'Profile'{rosters = RosterIds} = P} -> - P#'Profile'{rosters = lists:map(fun roster:roster/1, RosterIds), status = get}; -%% P#'Profile'{status = get, rosters = [element(2, kvs:get('Roster', X)) || X <- P#'Profile'.rosters]}; + {ok, #'Profile'{} = P} -> + Size = case catch binary_to_integer(roster:get_data_val(<<"size">>, Settings)) of + {'EXIT', _} -> []; V -> V end, + roster:send_profile( + P#'Profile'{status = get, update = LastSync, settings = roster_presence:amazon_settings(P)}, + Size, LastSync, ClientId, C), <<>>; {error, Reason} -> #io{code = #error{code = Reason}} end, -%% roster:info(?MODULE, "~p:Profile/get.Response:~p", [ClientId, R]), {reply, {bert, R}, Req, State}; -%%info(#'Profile'{phone = Phone, status = link, services = [#'Service'{type = email, id = _Addr} = Ser]}, Req, -%% #cx{params = ClientId} = State) -> -%% roster:info(?MODULE, "~p:~p:Profile/link", [Phone, ClientId]), -%% case kvs:get('Profile', Phone) of -%% {ok, P} -> -%%%% NOTE! Email should be unique! -%%%% TODO it in future -%% #'Profile'{services = ProfileSer} = P, -%% {ok, EmailCode} = telesign_api:generate_sms(Phone), -%% UpdSer = Ser#'Service'{data = EmailCode, status = added}, -%% UpdProfileSer = lists:keystore(email, #'Service'.type, ProfileSer, UpdSer), -%% kvs:put(P#'Profile'{services = UpdProfileSer, update = roster:now_msec(), status = update}), -%% n2o_async:pid(system, ?MODULE) ! {send_email, UpdSer}, -%% {reply, {bert, {io, {ok, email_sent}, <<>>}}, Req, State}; -%% {error, R} -> {reply, {bert, {io, {error, R}, <<>>}}, Req, State} end; -%% -%%info(#'Profile'{phone = Phone, status = email, services = [#'Service'{type = email, data = Code} = Ser]}, Req, -%% #cx{params = ClientId} = State) -> -%% roster:info(?MODULE, "~p:~p:Profile/email", [Phone, ClientId]), -%% case kvs:get('Profile', Phone) of -%% {ok, P} -> #'Profile'{services = ProfileSer} = P, -%% #'Service'{data = EmailCode} = lists:keyfind(email, #'Service'.type, ProfileSer), -%% case Code of -%% EmailCode -> -%% UpdProfileSer = lists:keystore(email, #'Service'.type, ProfileSer, Ser#'Service'{data = [], status = verified}), -%% kvs:put(P#'Profile'{services = UpdProfileSer, update = roster:now_msec(), status = update}), -%% {reply, {bert, {io, {ok, verified}, <<>>}}, Req, State}; -%% _ -> {reply, {bert, {io, {error, wrong_code}, <<>>}}, Req, State} -%% end; - -%% TODO maybe in future -%%info(#'Profile'{phone = Phone, status = aws}, Req, #cx{params = ClientId} = State) -> -%% roster:info(?MODULE, "~p:~p:Profile/aws", [Phone, ClientId]), -%% case kvs:get('Profile', Phone) of -%% {ok, _} -> -%% n2o_async:pid(system, ?MODULE) ! {aws, ClientId}, -%% {reply, {bert, {io, {ok, aws}, #'Service'{type = aws, status = added}}}, Req, State}; -%% {error, R} -> -%% roster:info(?MODULE, "~p:~p:Profile/aws.Failed:~p", [Phone, ClientId, R]), -%% {reply, {bert, {io, {error, aws}, <<>>}}, Req, State} end; info(#'Profile'{status = init} = Data, Req, State) -> {reply, {bert, Data}, Req, State}; @@ -98,7 +83,13 @@ info(#'Profile'{} = Data, Req, State) -> proc(init,#handler{name=?MODULE} = Async) -> roster:info(?MODULE, "ASYNC",[]), - {ok,Async}. + {ok,Async}; + +proc({restart, M}, #handler{state = {C, Proc}, seq = S} = H) -> + roster:info(?MODULE, "BPE PROC restarted", []), + roster:restart_module(M), + {reply, [], H}. + %% TODO maybe in future %%proc({send_email,#'Service'{id = Addr, data = Code}}, #handler{}=H) -> @@ -125,4 +116,26 @@ proc(init,#handler{name=?MODULE} = Async) -> %% roster:info(?MODULE, "CannotFindConnection:~p", [ClientId]); %% _-> roster:send_action(C, ClientId, Res) %% end, -%% {reply, [], H}. \ No newline at end of file +%% {reply, [], H}. + +del_roster(UID,RosterId) -> + PId=roster:phone_id(UID,RosterId), + case kvs:get('Roster', RosterId) of + {ok, #'Roster'{nick = Nick, roomlist=Rooms, userlist=Conts}} -> + roster:del_jobs(<<"publish">>, PId), + roster:unsubscribe_p2p(UID, RosterId), + roster:remove_member(PId), + roster:unsubscribe_muc(PId), + [roster:update_writer(#muc{name=Name},{#'Message'.from,PId},{#'Message'.files,[#'Desc'{payload = "Deleted"}]}) + || #'Room'{id=Name} <- Rooms], + [roster:update_writer(roster:feed_key(p2p,PId,FId),{#'Message'.from,PId},{#'Message'.files,[#'Desc'{payload = "Deleted"}]}) + || #'Contact'{phone_id = FId} <- Conts], + roster:update_chains({'Message',from},{#'Message'.from,PId},{#'Message'.files,[#'Desc'{payload = "Deleted"}]}), + kvs:delete('Index', {nick, string:lowercase(Nick)}), + case roster:is_online(user_id,PId) of offline -> roster:delete_sessions(user_id, PId); + online -> roster:force_logout(user_id,PId) end, + kvs:put(#'Roster'{id=RosterId, phone=UID, nick= <<"Deleted user">>, status=del}), + RosterId; + {error, _} -> [] + end +. \ No newline at end of file diff --git a/apps/roster/src/protocol/roster_room.erl b/apps/roster/src/protocol/roster_room.erl index c47db9d431f7117cd0ba3c7c85e4a9f9d15d6cfa..e69babdee59807def124156672fa7097798cb6fe 100644 --- a/apps/roster/src/protocol/roster_room.erl +++ b/apps/roster/src/protocol/roster_room.erl @@ -1,38 +1,46 @@ -module(roster_room). -include("roster.hrl"). -include_lib("n2o/include/n2o.hrl"). --include_lib("roster/static/room_channel_text.hrl"). +-include_lib("roster/include/static/roster_text.hrl"). -compile(export_all). +-import(roster_link,[gen_link/1]). + -define(MAX_ROOM_LENGTH, 32). -define(MIN_ROOM_LENGTH, 1). start() -> n2o_async:start(#handler{module = ?MODULE, class = system, group = roster, name = ?MODULE, state = []}). -info(#'Room'{status = create, id = Room, admins = Admins, members = Members} = R, Req, - #cx{params = ClientId, state = verified} = State) -> - kvs_stream:save(#writer{id = Feed = #muc{name = Room}}), - PhoneId = roster:phone_id(ClientId), - Adm = case lists:keyfind(PhoneId, #'Member'.phone_id, Admins ++ Members) of - #'Member'{} = A -> A; - false -> #'Member'{phone_id = PhoneId} end, - Admin = Adm#'Member'{id = [], feed_id = Feed, status = admin, - update = roster:now_msec()}, +info(#'Room'{status = create, id = Room, admins = [Admin|_]} = R, Req, + #cx{state = verified} = State) -> + kvs_stream:save(#writer{id = #muc{name = Room}}), roster:subscribe_room(Admin#'Member'{presence = online}), - kvs:put(R#'Room'{status = [], id = Room, members = [], admins = [], created = roster:now_msec()}), - info(R#'Room'{status = add, id = Room, - admins = [Admin | lists:keydelete(PhoneId, #'Member'.phone_id, Admins)], - members = lists:keydelete(PhoneId, #'Member'.phone_id, Members)}, - Req, State#cx{state = create}); -info(#'Room'{status = create, id = Room, name = Name, admins = [_|TA]= Admins, members = Members, data = Data} = R, Req, - #cx{params = ClientId} = State) when Room /= [] andalso not (TA == [] andalso Members == []) -> + Link = gen_link(Room), + ok = kvs:put(R#'Room'{status = [], id = Room, links = [Link], members = [], admins = [], created = roster:now_msec()}), + 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|TA]= Admins, members = Members, data = Data} = R, Req, + #cx{params = ClientId} = State) -> Length = length(unicode:characters_to_list(Name)), roster:info(?MODULE, "~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 - andalso [] == (catch roster:verify_descs(Data)) - andalso roster:is_unimembers(Admins ++ Members) of - true -> info(R, Req, State#cx{state = verified}); + andalso roster:is_unimembers(Admins ++ Members) + andalso not (Prefix == <<"emqttd">> andalso TA == [] andalso Members == []) of + true -> + #'Member'{phone_id = OwnPId} = Adm = case ClientId of + <<"emqttd_", _/binary>> -> + PhoneId = roster:phone_id(ClientId), + case lists:keyfind(PhoneId, #'Member'.phone_id, Admins ++ Members) of + #'Member'{} = A -> A; + false -> #'Member'{phone_id = PhoneId} end; + <<"sys_", _/binary>> -> Owner + end, + Admin = Adm#'Member'{id = [], status = admin, feed_id = #muc{name = Room}, update = roster:now_msec()}, + info(R#'Room'{admins = [Admin|lists:keydelete(OwnPId, #'Member'.phone_id, Admins)], + members = lists:keydelete(OwnPId, #'Member'.phone_id, Members)}, + Req, State#cx{state = verified}); _ -> {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 /= [] -> @@ -53,7 +61,7 @@ info(#'Room'{status = patch, id = Room, name = Name, data = AvatarDesc} = R, Req NewName when NewName /= [] andalso NewName /= OldName andalso NewName /= <<>> -> %% send system msg about new group name #'Message'{id = NewNameLastMsgId} = NewNameMsg = roster:add_message( - #'Message'{feed_id = RoomFeed, from = PId, to = Room, + #'Message'{feed_id = RoomFeed, from = PId, to = Room, msg_id = roster:generate_server_id(), files = [#'Desc'{payload = iolist_to_binary([?SYS_MSG_UPDATE_GROUP_NAME, " \"", NewName, "\""])}], type = [sys], status = []}, roster:get_reader(RoomFeed, PId)), roster:send_room(C, Room, NewNameMsg), @@ -69,7 +77,7 @@ info(#'Room'{status = patch, id = Room, name = Name, data = AvatarDesc} = R, Req %% send system msg about new group avatar #'Message'{id = NewAvatarLastMsgId} = NewAvatarMsg = roster:add_message( #'Message'{feed_id = RoomFeed, from = PId, to = Room, - files = [ + msg_id = roster:generate_server_id(), files = [ #'Desc'{payload = iolist_to_binary([?SYS_MSG_UPDATE_GROUP_AVATAR])}, #'Desc'{payload = AvatarPayload, mime = AvatarMime}], type = [sys], status = []}, roster:get_reader(RoomFeed, PId)), @@ -103,122 +111,168 @@ info(#'Member'{status = patch, id = Id} = Member, Req, #cx{params = ClientId, fr #error{} -> {#io{code = #error{code = member_not_found}}, From} end, {reply, {bert, IO}, Req, State#cx{from = Topic}}; -info(#'Room'{status = add, id = Room, members = Members, admins = Admins, readers = HL}, Req, - #cx{params = ClientId, client_pid = C, state = RStatus} = State) when Members /= []; Admins /= [] -> - roster:info(?MODULE, "~p:Room/add:~p", [ClientId, Room]), - {reply, {bert, - case kvs:get('Room', Room) of - {ok, #'Room'{} = StoredRoom} -> - case {roster:muc_member(APId = roster:phone_id(ClientId), Room), RStatus} of - {Mmbr, _} when RStatus == create orelse - is_record(Mmbr, 'Member') andalso Mmbr#'Member'.status == admin -> - {Mmbrs, Aliases, NewMmbrs, StoredRoom2} = - lists:foldl( - fun(#'Member'{phone_id = PhoneId, status = Status} = Member, {Ms, Alss, NewMs, TmpRoom} = MAcc) - when Status == admin; Status == member -> - MmbrRoom = - case roster:muc_member(PhoneId, Room, presence) of - #'Member'{phone_id = APId} = M -> {ignore, M}; - #'Member'{status = removed} = M when Status == admin; Status == member -> - M2 = roster:patch_member(M#'Member'{status = Status}, Member), - {M3, _, UpdRoom} = roster:add_member(TmpRoom, M2, {no_muc_message, HL}), - kvs:put(M3), - {M3, UpdRoom}; - #'Member'{} = M -> - kvs:put(M2 = roster:patch_member(M#'Member'{status = Status}, Member)), - {ignore, M2}; - _ -> {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; - {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 - end, {[], [], [], StoredRoom}, Admins ++ Members), - {Admins2, Members2} = roster:split_members(Mmbrs), - {Payload, RStatus2} = - case {RStatus, Aliases} of - {create, _} -> - {<>, create}; - {_, []} -> {[], add}; - _ -> - {iolist_to_binary(["added by ", APId, ": ", lists:droplast(Aliases)]), add} end, - {AdmMember = #'Member'{reader = Reader}, Admins3} = - case {Mmbr, lists:keyfind(APId, #'Member'.phone_id, Admins2)} of - {#'Member'{} = AMmbr, false} -> {AMmbr, [AMmbr|Admins2]}; - {#'Member'{} = AMmbr, _} -> {AMmbr, Admins2}; - {_, #'Member'{} = AMmbr} -> {AMmbr, Admins2} end, +info(#'Room'{status = St, id = Room, members = Members, admins = Admins, readers = HL}, Req, + #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]), + 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; + <<"emqttd">> -> roster:phone_id(ClientId); + _ -> [] + end, + IO = case kvs:get('Room', Room) of + {ok, #'Room'{} = StoredRoom} -> + case roster:muc_member(APId, Room) of + Mmbr when RStatus == create orelse St == join orelse + (is_record(Mmbr, 'Member') andalso (Mmbr#'Member'.status == admin) orelse + 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) + when Status == admin; Status == member -> + MmbrRoom = + case roster:muc_member(PhoneId, Room, presence) of + #'Member'{status = removed} = M when Status == admin; Status == member-> + M2 = roster:patch_member(M#'Member'{status = Status}, Member), + {M3, _, UpdRoom} = roster:add_member(TmpRoom, M2, {no_muc_message, HL}), + kvs:put(M3), + {M3, UpdRoom}; + #'Member'{phone_id = APId} = M -> {ignore, M}; + #'Member'{} = M -> + kvs:put(M2 = roster:patch_member(M#'Member'{status = Status}, Member)), + {ignore, M2}; + _ -> {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; + {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 + end, {[], [], [], StoredRoom}, Admins ++ Members), + {Admins2, Members2} = roster:split_members(Mmbrs), + {Payload, RStatus2} = + case {RStatus, Aliases, St} of + {create, _, _} -> + {<>, create}; + {_, [], add} -> {[], add}; + {_, _, join} -> {<>, join}; + _ -> {iolist_to_binary(["added by ", APId, ": ", lists:droplast(Aliases)]), add} end, + + {AdmMember = #'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), []}; + {#'Member'{} = AMmbr, false} -> {AMmbr, [AMmbr|Admins2]}; + {#'Member'{} = AMmbr, _} -> {AMmbr, Admins2}; + {_, #'Member'{} = AMmbr} -> {AMmbr, Admins2} end, + + R = StoredRoom2#'Room'{update = roster:now_msec()}, - R = StoredRoom2#'Room'{update = roster:now_msec()}, - R2 = case Mmbrs of [#'Member'{phone_id = APId}] -> R;[] -> 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, - 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}, - [ 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), <<>>; - {[], _} -> #io{code = #error{code = permission_denied}} end; - {error, _} -> #io{code = #error{code = room_not_found}} end}, Req, State}; + R2 = case Mmbrs of [#'Member'{phone_id = APId}] when Prefix /= <<"sys">> -> R;[] -> 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:generate_server_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}, + [ 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), <<>>; + [] -> #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 = Admins, id = Room}, Req, +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]), - {reply, {bert, - case kvs:get('Room', Room) of - {ok, #'Room'{} = StoredRoom} -> - case roster:muc_member(ClientId, Room) of - #'Member'{status = admin, reader = Reader} = AdmMember-> - APId = roster:phone_id(ClientId), - {Mmbrs, Aliases, Rosters} = lists:foldr(fun(#'Member'{phone_id = PhoneId}, {Ms, Alss, Rs} = Acc) -> - case roster:muc_member(PhoneId, Room, presence) of + {APId, Admins} = case hd(binary:split(ClientId, <<"_">>)) of + <<"sys">> -> {(hd(Admins0))#'Member'.phone_id, []}; + <<"emqttd">> -> {roster:phone_id(ClientId), Admins0} + end, + IO = case kvs:get('Room', Room) of + {ok, #'Room'{} = StoredRoom} -> + case roster:muc_member(APId, Room) of + #'Member'{status = admin, reader = Reader} = AdmMember-> +%% APId = roster:phone_id(ClientId), + {Mmbrs, Aliases, Rosters} = + lists:foldr(fun(#'Member'{phone_id = PhoneId}, {Ms, Alss, Rs} = Acc) -> + case roster:muc_member(PhoneId, Room, presence) of %% #'Member'{status = admin} = M when Status == member -> M; %% TODO can admin remove other admin? - #'Member'{feed_id = Feed, alias = Alias} = M -> - roster:unsubscribe_room(PhoneId), - {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), - kvs:put(M2 = M#'Member'{status = removed, update = roster:now_msec()}), - {[M2 | Ms], [Alias, <<",">> | Alss], [Roster2 | Rs]}; - 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, - 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}), - [roster:update_rooms(Rstr, R#'Room'{status = removed}) || Rstr <- Rosters], - R2 = roster:reader_cache(R#'Room'{status = remove, last_msg = [], - members = Members2, admins = Admins3}), - roster:send_room(C, R2), - [roster:send_ses(C, roster:phone(PhoneId), Rec) - || #'Member'{phone_id = PhoneId} <- Mmbrs, Rec <- [R2, Msg]], - n2o_async:pid(system, ?MODULE) ! {send_push, Mmbrs, Msg, remove}, - roster:send_room(C, Room, Msg); - #'Member'{} -> #io{code = #error{code = permission_denied}}; - _ -> #io{code = #error{code = member_not_found}} end; - {error, _} -> #io{code = #error{code = room_not_found}} end}, - Req, State}; + #'Member'{feed_id = Feed, alias = Alias} = M -> + roster:unsubscribe_room(PhoneId), + {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), + kvs:put(M2 = M#'Member'{status = removed, update = roster:now_msec()}), + {[M2 | Ms], [Alias, <<",">> | Alss], [Roster2 | Rs]}; + 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:generate_server_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}), + [roster:update_rooms(Rstr, R#'Room'{status = removed}) || Rstr <- Rosters], + R2 = roster:reader_cache(R#'Room'{status = remove, last_msg = [], + members = Members2, admins = Admins3}), + roster:send_room(C, R2), + [roster:send_ses(C, roster:phone(PhoneId), Rec) + || #'Member'{phone_id = PhoneId} <- Mmbrs, Rec <- [R2, Msg]], + n2o_async:pid(system, ?MODULE) ! {send_push, Mmbrs, Msg, remove}, + roster:send_room(C, Room, Msg); + #'Member'{} -> #io{code = #error{code = permission_denied}}; + _ -> #io{code = #error{code = member_not_found}} end; + {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 + end, + {reply, {bert, <<>>}, Req, State}; info(#'Room'{status = leave, id = Room}, Req, #cx{params = ClientId, client_pid = C} = State) -> roster:info(?MODULE, "~p:Room/leave:~p", [ClientId, Room]), @@ -236,8 +290,9 @@ info(#'Room'{status = leave, id = Room}, Req, #cx{params = ClientId, client_pid R = roster:reader_cache(Room2#'Room'{status = leave, update = roster:now_msec(), last_msg = []})), UpdRoom2 = case Status of removed -> Room2; - _ -> #writer{cache = Msg} = roster:add_message(#'Message'{feed_id = #muc{name = Room}, from = PhoneId, - to = Room, status = [], type = [sys], files = [#'Desc'{payload = <>}]}), + _ -> #writer{cache = Msg} = roster:add_message(#'Message'{feed_id = #muc{name = Room}, + from = PhoneId, msg_id = roster:generate_server_id(), to = Room, status = [], + type = [sys], files = [#'Desc'{payload = <>}]}), roster:send_room(C, R), roster:send_room(C, Room, Msg), n2o_async:pid(system, ?MODULE) ! {send_push, [], Msg, leave}, @@ -291,7 +346,6 @@ info(#'Member'{} = Member, Req, #cx{params = ClientId} = State) -> proc(init, #handler{name = roster_room} = Async) -> roster:info(?MODULE, "ASYNC ROOM PROC started", []), {ok, Async}; - %% TODO use roster_push instead proc({send_push, NewMembers, Msg, Type}, #handler{} = H) -> PushType = <<"message">>, @@ -315,7 +369,7 @@ proc({send_push, NewMembers, Msg, Type}, #handler{} = H) -> Payload = roster:room(roster:roster_id(MemPhoneId), RoomId), PushAlert = case Type of - T when T == add; T == remove -> + T when T == add; T == remove;T == join -> MemberPayload = case lists:member(Mem, NewMembersData) of true -> MemPay = lists:foldl(fun({_, Al}, Ac) -> @@ -331,6 +385,8 @@ proc({send_push, NewMembers, Msg, Type}, #handler{} = H) -> iolist_to_binary([FromAlias | case T of add -> [<<" invited ">>, MemberPayload, <<" to the group ">>, RoomName]; + join -> + [<<" joined ">>, MemberPayload, <<" to the group ">>, RoomName]; remove -> [<<" removed ">>, MemberPayload, <<" from the group ">>, RoomName] end]); create -> @@ -340,16 +396,6 @@ proc({send_push, NewMembers, Msg, Type}, #handler{} = H) -> T when T == photo; T == name -> iolist_to_binary([FromAlias, <<" edited the group's ">>, RoomName, <<" ">>, atom_to_list(T)]) end, - roster:info(?MODULE, "~p:~p:~pPushAlert:~p", [MemPhoneId, Os, binary:part(Push, 0, erlang:min(25, size(Push))), PushAlert]), - case Os of - ios -> - DecodedMsg = base64:encode(term_to_binary(Payload)), - push_api:apns_notify(PushAlert, DecodedMsg, PushType, Push, AuthSettings); - android -> - AndroidPush = http_uri:encode(binary_to_list(base64:encode(term_to_binary(#push{model = Payload, - type = PushType, alert = PushAlert, title = PushAlert, badge = 1})))), - push_api:fcm_notify(PushAlert, AndroidPush, Push) - end - end || {MemPhoneId, _} = Mem <- ToRoomMembers, - #'Auth'{os = Os, push = Push, settings = AuthSettings} <- kvs:index('Auth', user_id, MemPhoneId), lists:member(Os, [ios, android]) + 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 diff --git a/apps/roster/src/protocol/roster_roster.erl b/apps/roster/src/protocol/roster_roster.erl index 575a189e469f44e78c0e120c202bc782c7168270..bdc3bdb1dabe77f9e3ed06a2c840f3be2d56d0e8 100644 --- a/apps/roster/src/protocol/roster_roster.erl +++ b/apps/roster/src/protocol/roster_roster.erl @@ -11,9 +11,9 @@ start() -> n2o_async:start(#handler{module = ?MODULE, class = system, group = ro info(#'Roster'{status = patch, avatar = Avatar} = Data, Req, #cx{params = ClientId, client_pid = C, - state = {verified, #'Roster'{id = RosterId, phone = Phone, + state = {verified, #'Roster'{id = RosterId0, phone = Phone, avatar = OldAvatar, roomlist = Rooms, status = Status} = Roster}} = State) -> - RosterId = roster:roster_id(ClientId), + 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])]), IO = case PreUpdRoster = roster:patch_roster(Roster, Data) of #error{code = invalid_data} = Err -> #io{code = Err}; @@ -36,7 +36,8 @@ info(#'Roster'{status = patch, avatar = Avatar} = Data, Req, end, <<>> end, {reply, {bert, IO}, Req, State#cx{state = []}}; -info(#'Roster'{status = patch, names = Names, surnames = Surnames, avatar = Avatar} = Data, Req, #cx{params = ClientId} = State) -> +info(#'Roster'{id = RosterId0, status = patch, names = Names, surnames = Surnames, avatar = Avatar} = Data, + Req, #cx{params = ClientId} = State) -> Ns = [case {Key, length(unicode:characters_to_list(Name))} of {_, _} when Name == [] -> empty; {_, 0} -> 0; @@ -45,7 +46,8 @@ info(#'Roster'{status = patch, names = Names, surnames = Surnames, avatar = Avat _ -> valid end || {Key, Name} <- lists:zip([names, surnames, avatar],[Names, Surnames, Avatar])], - case kvs:get('Roster', roster:roster_id(ClientId)) of + RosterId = case ClientId of <<"sys_", _/binary>> -> RosterId0; _ -> roster:roster_id(ClientId) end, + case kvs:get('Roster', RosterId) of {error, Reason} -> #io{code = #error{code = Reason}}; {ok, #'Roster'{names = StoreNames, surnames = StoreSurnames, avatar = StoreAvatar} = Roster} -> case {StoreNames, StoreSurnames, StoreAvatar, Ns} of @@ -76,8 +78,8 @@ info(#'Roster'{nick = NickToBeValidated, status = nick} = Data, Req, #cx{params #io{code = #error{code = invalid_nick}}; {ok, Nick} -> %% set processing data to lowercase - OldNickLow = string:lowercase(OldNick), - NickLow = string:lowercase(Nick), + OldNickLow = list_to_binary(string:to_lower(nitro:to_list(OldNick))), + NickLow = list_to_binary(string:to_lower(nitro:to_list(Nick))), NickValidationError = case NickLow of [] -> false; _ -> @@ -140,6 +142,7 @@ info(#'Roster'{id=RosterId, status=get},Req, #cx{params = ClientId }=State) -> %% {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)]), {reply, {bert, #io{code = case roster:remove_contacts(RosterId, List, ClientId) of diff --git a/apps/roster/src/protocol/roster_search.erl b/apps/roster/src/protocol/roster_search.erl index dee58326355cf1e6aa6356a91b6197e4e4344ef5..95cbc651acdd862a374aec5eb14685d1f4972401 100644 --- a/apps/roster/src/protocol/roster_search.erl +++ b/apps/roster/src/protocol/roster_search.erl @@ -25,7 +25,7 @@ info(#'Search'{id = From, ref = Ref, field = <<"nick">>, type = '==', value = Q, _ -> [] end, L = case PhoneId of [] -> []; _ -> [begin - Rs = case kvs:get('Index', {nick, string:lowercase(V)}) of + Rs = case kvs:get('Index', {nick, list_to_binary(string:to_lower(nitro:to_list(V)))}) of {ok, #'Index'{roster = IDs}} -> IDs; _ -> [] end, @@ -85,8 +85,8 @@ index(R, K) -> Start = mnesia:dirty_first(R), case Start of {K, V} -> catch ets:insert(K, [{binary_to_list(V)}]), - index_next(R, K, Start); - _ -> skip + index_next(R, K, Start); + _ -> ok end. index_next(R, K, Current) -> @@ -94,8 +94,9 @@ index_next(R, K, Current) -> '$end_of_table' -> ok; Next -> - [#'Index'{id = {K, V}}] = mnesia:dirty_read({R, Next}), - catch ets:insert(K, [{binary_to_list(V)}]), %% TODO - index_next(R, K, Next), - ok + case mnesia:dirty_read({R, Next}) of + [#'Index'{id = {K, V}}] -> catch ets:insert(K, [{binary_to_list(V)}]); %% TODO + _ -> skip + end, + index_next(R, K, Next),ok end. \ No newline at end of file diff --git a/apps/roster/src/rest/admin_whitelist.erl b/apps/roster/src/rest/admin_whitelist.erl index 019275892daa28b683c1828637a5f06e80b9b5c5..6df46ca442acfb29bf77175cf03925aba1617d9a 100644 --- a/apps/roster/src/rest/admin_whitelist.erl +++ b/apps/roster/src/rest/admin_whitelist.erl @@ -1,9 +1,10 @@ -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({200, [{"Content-Type", "text/html"}], HTMLOutput}). \ No newline at end of file + 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 new file mode 100644 index 0000000000000000000000000000000000000000..bf1a0a0eb81e2c4ee4a3d3d53867d5cea43eb5dc --- /dev/null +++ b/apps/roster/src/rest/helpers/rest_auth_helper.erl @@ -0,0 +1,54 @@ +-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 "api" == RootPathElem of %% If the root path of url is 'api' look for Bearer Authentication + true -> + case Req:get_header_value("Authorization") of + "Bearer " ++ BearerAuth -> + try + [T, CId] = string:split(BearerAuth, "/"), + AuthToken = list_to_binary(T), + ClientId = list_to_binary(CId), + case roster:parse_token(AuthToken) of + {error, _} = Err -> Err; + _ -> + case kvs:get('Auth', ClientId) of + #ok{code = #'Auth'{token = DBToken}} when DBToken == AuthToken -> {true, api}; + {ok, #'Auth'{token = DBToken}} when DBToken == AuthToken -> {true, api}; + _ -> {error, invalid_token} + 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_log_helper.erl b/apps/roster/src/rest/helpers/rest_log_helper.erl new file mode 100644 index 0000000000000000000000000000000000000000..357b8414e8a8b86fa4d77dfc83bf246c76568fe6 --- /dev/null +++ b/apps/roster/src/rest/helpers/rest_log_helper.erl @@ -0,0 +1,10 @@ +-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 new file mode 100644 index 0000000000000000000000000000000000000000..efeabd715ba7aad5bcaab7fa5012cb40e3b51c73 --- /dev/null +++ b/apps/roster/src/rest/helpers/rest_main_helper.erl @@ -0,0 +1,146 @@ +-module(rest_main_helper). +-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, + start_emqttd_process/0, rest_pid/0, send_to_mqtt/1, send_to_mqtt/2, + get_query_value/2, get_body_value/2, get_body_values/2, get_query_values/2, get_request_headers/2, + check_params_missing/2, + is_base64/1, validate_msg_to_publish/1, + get_profile_by_phone/1, get_phone_number/1, get_client_phone_id/1 +]). + +description() -> "Rest Helpers Module". + +start_emqttd_process() -> +%% start emqttd process and set it pid to application variables + {ok, C} = emqttc:start_link([{client_id, ?SYS_REST_CLIENT}, {logger, {console, error}}, {reconnect, 5}]), + application:set_env(rest, rest_pid, C), + C. + +rest_pid() -> +%% get emqttd process id (pid) for mqtt publishing + case application:get_env(rest, rest_pid) of + {ok, C} -> case process_info(C) of undefined -> start_emqttd_process(); _ -> C end; + _ -> start_emqttd_process() + end. + +send_to_mqtt(Msg) -> + roster:send_event(rest_pid(), ?SYS_REST_CLIENT, <<>>, Msg, roster:get_vnode(?SYS_REST_CLIENT, Msg)), + ok. + +send_to_mqtt(sync, Msg) -> +%% NOTE! Debug info is commented under the message +%% {cx,[],[],[],api,[],[],<<>>,bert,<<"sys_rest">>,<<"4">>,<0.1067.0>,[], <<"actions/1/api/sys_rest">>,<<"1">>} +%% #cx{handlers = [],actions = [],req = [],module = api, +%% lang = [],path = [],session = <<>>,formatter = bert, +%% params = <<"sys_rest">>,node = <<"4">>, +%% client_pid = <0.1067.0>,state = [], +%% from = <<"actions/1/api/sys_rest">>,vsn = <<"1">>} + roster_proto:info(Msg, [], #cx{params = ?SYS_REST_CLIENT, client_pid = rest_pid()}), + ok. + +get_query_value(QuerySet, Key) -> + case lists:keyfind(Key, 1, QuerySet) of + false -> {error, ?ERROR_MISSING_PARAM}; + {Key, Value} -> {ok, Value} + end. + +get_body_value(ReqBody, Key) -> + case to_json(ReqBody) of + R = {error, _} -> R; + {ok, JsonReqBody} -> + case proplists:get_value(nitro:to_binary(Key), JsonReqBody) of + undefined -> {error, ?ERROR_MISSING_PARAM}; + Val -> {ok, Val} + end + end. + +get_body_values(ReqBody, Keys) -> + lists:foldl(fun(Key, Acc) -> + case get_body_value(ReqBody, Key) of + {error, _} -> Acc; + {ok, Val} -> Acc ++ [{nitro:to_binary(Key), Val}] + end + end, [], Keys). + +get_query_values(ReqBody, Keys) -> + lists:foldl(fun(Key, Acc) -> + case get_query_value(ReqBody, Key) of + {error, _} -> Acc; + {ok, Val} -> Acc ++ [{nitro:to_binary(Key), Val}] + end + end, [], Keys). + +get_request_headers(Req, Keys) -> + lists:foldl(fun(Key, Acc) -> + case Req:get_header_value(Key) of + undefined -> Acc; + Val -> Acc ++ [{nitro:to_binary(Key), Val}] + end + end, [], Keys). + +check_params_missing(ExpectedParams, ActualParams) -> + if + length(ExpectedParams) /= length(ActualParams) -> {error, ?ERROR_MISSING_PARAM}; + length(ExpectedParams) == length(ActualParams) -> {ok, ActualParams} + end. + + +to_json(ReqBody) -> + case jsx:is_json(ReqBody) of + false -> {error, ?ERROR_INVALID_JSON}; + _ -> {ok, jsx:decode(ReqBody)} + end. + +%% TODO refactor it. Join with is_base64 fun if it is possible +validate_msg_to_publish(Data) -> +%% check is message data base64 encodded bytearray + try + binary_to_list(base64:decode(Data)) + catch E:R -> + roster:info(?MODULE, "~p:~pBase64 Decoding Error! ~p", [E, R, Data]), + false + end. + +is_base64(Data) -> + try + base64:decode(Data), + true + catch E:R -> + roster:info(?MODULE, "~p:~pBase64DecodingError! ~p", [E, R, Data]), + false + end. + +%% TODO remove this method to DB helper + +get_profile_by_phone(PhoneNumber) -> + case kvs:get('Profile', nitro:to_binary(PhoneNumber)) of + ErrorResponse = {error, _} -> ErrorResponse; + SuccessResponse = {ok, _} -> SuccessResponse + end. + +get_phone_number(PhoneId) when not is_binary(PhoneId) -> + get_phone_number(nitro:to_binary(PhoneId)); +get_phone_number(PhoneId) -> +%% get phone number from phone id + case roster:parts_phone_id(PhoneId) of + {error, _} -> PhoneId; + [Phone, _] -> Phone + end. + +get_client_phone_id(Req) -> + case Req:get_header_value("Authorization") of + "Bearer" ++ BearerAuth -> + [_, CId] = string:split(BearerAuth, "/"), + case kvs:get('Auth', list_to_binary(CId)) of + #ok{code = #'Auth'{user_id = User}} -> User; + {ok, #'Auth'{user_id = User}} -> User; + _ -> throw(authentication_error) + end; + _ -> + throw(authentication_error) + end. \ No newline at end of file diff --git a/apps/roster/src/rest/helpers/rest_response_helper.erl b/apps/roster/src/rest/helpers/rest_response_helper.erl new file mode 100644 index 0000000000000000000000000000000000000000..cf1c29ff19acef2d7ceba649b9b903a98b81b9fe --- /dev/null +++ b/apps/roster/src/rest/helpers/rest_response_helper.erl @@ -0,0 +1,106 @@ +-module(rest_response_helper). +-include_lib("roster/include/static/rest_text.hrl"). +-include_lib("roster/include/static/rest_var.hrl"). + +-export([ + description/0, + response/3, response/4, + error_400/0, error_401/0, error_404/0, error_405/0, + error_400_response/1, error_405_response/1, + error_response/0, error_response/1, error_response/2, error_response_api/3, + success_response/0, success_response/1, success_response/2, + success_200/0 +]). + +description() -> "Rest Responses Helper". + +response(Req, Status, Body) -> + response(Req, Status, Body, "application/json"). +%% respond with application/json content type +response(Req, Status, Body, ContentType) -> + Req:respond({Status, [{"Content-Type", ContentType}, {"WWW-Authenticate", "Basic realm=\"nynjaApp\""}], Body}). + +%% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +%% ERRORS +%% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +%% Static + +error_400() -> + error_response(?HTTP_CODE_400, ?ERROR_400). + +error_401() -> + error_response(?HTTP_CODE_401, ?ERROR_401). + +error_404() -> + error_response(?HTTP_CODE_404, ?ERROR_404). + +error_405() -> + error_response(?HTTP_CODE_405, ?ERROR_405). + +%% Dynamic + +error_response() -> + error_response([], []). +error_response(Msg) -> + error_response([], Msg). +error_response([], Msg) -> + error_response(?HTTP_CODE_400, Msg); +error_response(Status, []) -> + error_response(Status, ?ERROR_400); +error_response(Status, Msg) -> + response_json(Status, jsx:encode([{<<"message">>, nitro:to_binary(Msg)}])). + + +error_response_api(Req, Status, Message) -> + roster:info(?MODULE, "NotFound:~p:~p:~p, Error: ~p", [Req:get(method), Req:get(path), Req:parse_post(), Message]), + Body = response_json(Status, 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()]), + 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)]), + response(Req, ?HTTP_CODE_405, rest_response_helper:error_405()). + +%% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +%% SUCCESS +%% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +%% Static + +success_200() -> + success_response([], []). + +%% Dynamic + +success_response() -> + success_response(?HTTP_CODE_200, []). +success_response(Msg) -> + success_response(?HTTP_CODE_200, Msg). +success_response([], Msg) -> + success_response(?HTTP_CODE_200, Msg); +success_response(Status, Msg) -> + response_json(Status, Msg). + +%% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +%% INTERNAL HELPERS +%% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + +response_json(Status, Msg) -> + remove_symbol_screening(jsx:encode([{<<"status">>, Status}, {<<"data">>, Msg}])). + +remove_symbol_screening(Data) -> + nitro:to_binary( + re:replace(re:replace(re:replace(re:replace(re:replace(re:replace(re:replace(re:replace(Data, + "\\\"\\[", "\\[", [{return, list}, global]), + "\\\\", "", [{return, list}, global]), + "\\\"{", "{", [{return, list}, global]), + "}\\\"", "}", [{return, list}, global]), + "\"}\",{", "},{", [{return, list}, global]), + "\"}{\"", "\"},{\"", [{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 new file mode 100644 index 0000000000000000000000000000000000000000..cb5c898b4106b93ef2f0e142fb15fb7c0437f3bf --- /dev/null +++ b/apps/roster/src/rest/rest_chat_csv.erl @@ -0,0 +1,217 @@ +%%%----------------------------------------------------------------------------- +%%% @doc API module for converting chat history to CSV file. +%%% +%%% Here we expose a POST request for generating csv file from a private chat room. +%%% The POST request requires phone numbers + ids of both users. +%%% +%%% @see rest_handler:handle_request/3 +%%% @end +%%%----------------------------------------------------------------------------- +-module(rest_chat_csv). +-include("roster.hrl"). +-include_lib("roster/include/static/rest_var.hrl"). +-include_lib("kvs/include/metainfo.hrl"). + +-define(MIN_FROM, 0). +-define(MAX_TO, 9999999999999). + +-export([ + handle_request/3 +]). + +%% Custom data types +-type type_chat_element() :: {Date::string(), Time::string(), 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 := ToUser}, Req) -> + ReqData = Req:parse_qs(), + {FromDate, ToDate, AllowedMessageTypes} = parse_get(ReqData), + try + ToPhoneId = list_to_binary(ToUser), + 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)), + Members = [{FromPhoneId, FromUsername}, {ToPhoneId, ToUsername}], + ChatData = extract_chat_data(ChatHistory, Members, FromDate, ToDate, AllowedMessageTypes), + CsvText = conversation_to_csv(ChatData, ToUsername), + Filename = ["NynjaPrivateChat-", FromUsername, "-", ToUsername, ".csv"], + send_response(CsvText, Filename, Req) + 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_username_match, PhoneId} -> + rest_response_helper:error_response_api(Req, ?HTTP_CODE_404, "No User matches phone_id: " ++ PhoneId); + throw:{no_username, Id} -> + rest_response_helper:error_response_api(Req, ?HTTP_CODE_404, "No User with id: " ++ Id); + throw:authentication_error -> + rest_response_helper:error_response_api(Req, ?HTTP_CODE_401, "Couldn't access client_id!"); + _:_ -> + rest_response_helper:error_response_api(Req, ?HTTP_CODE_404, "Something went wrong!") + end; + +handle_request('GET', #{room := Group}, Req) -> + ReqData = Req:parse_qs(), + {FromDate, ToDate, AllowedMessageTypes} = parse_get(ReqData), + RoomId = list_to_binary(Group), + try + ChatHistory = get_chat_history({muc, RoomId}), + Members = get_group_chat_members(RoomId), + GroupChatName = get_group_chat_name(RoomId), + ChatData = extract_chat_data(ChatHistory, Members, FromDate, ToDate, AllowedMessageTypes), + CsvText = conversation_to_csv(ChatData, GroupChatName), + Filename = ["NynjaGroupChat-", GroupChatName, ".csv"], + 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_username_match, PhoneId} -> + rest_response_helper:error_response_api(Req, ?HTTP_CODE_404, "No member matches phone_id: " ++ PhoneId); + throw:{no_muc_name, RoomId} -> + rest_response_helper:error_response_api(Req, ?HTTP_CODE_404, "No group chat is associated with room_id: " ++ RoomId); + throw:{no_room, RoomId} -> + rest_response_helper:error_response_api(Req, ?HTTP_CODE_404, io_lib:format("Group chat with room_id: ~s doesn't exist", [RoomId])); + _:_ -> + rest_response_helper:error_response_api(Req, ?HTTP_CODE_404, "Something went wrong!") + end; + +handle_request(_, _, Req) -> + rest_response_helper:error_405_response(Req). + +%%%============================================================================= +%%% 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}). + +-spec parse_incomming_data(list(tuple())) -> {ok, term()} | {error, wrong_data}. +parse_incomming_data([{<<"from">>, FromPhoneId}, {<<"to">>, ToPhoneId}]) -> {ok, {FromPhoneId, ToPhoneId}}; +parse_incomming_data([{<<"muc">>, RoomId}]) -> {ok, RoomId}; +parse_incomming_data(_) -> {error, wrong_data}. + +parse_get(ReqParams) -> + From = proplists:get_value("from", ReqParams, ?MIN_FROM), + To = proplists:get_value("to", ReqParams, ?MAX_TO), + AllowedMessageTypes = proplists:get_keys(proplists:delete("from", proplists:delete("to", ReqParams))), + {From, To, AllowedMessageTypes}. + +% -spec conversation_to_csv(ChatHistory::type_chat_history(), Members::list(type_user_data()), CsvColumnsData::string()) -> string(). +conversation_to_csv(ChatData, CsvColumnsData) -> + ChatSpan = get_chat_time_span(ChatData), + CsvColumns = build_csv_columns(ChatSpan, CsvColumnsData), + build_csv_text(ChatData, CsvColumns). + +-spec build_csv_text(type_chat(), Acc::string()) -> string(). +build_csv_text([{Date, Time, Username, Message} | T], Acc) -> + build_csv_text(T, [Acc, [$\n, Date, $,, Time, $,, Username, $,, Message]]); +build_csv_text([], Acc) -> + lists:flatten(Acc). + +-spec build_csv_columns({FirstDate::binary(), LastDate::binary()}, ToUsername::string()) -> string(). +build_csv_columns({FirstDate, LastDate}, ToUsername) -> + [ + "NYNJA Chat Export", $\n, $\n, + "From Date:,", FirstDate, $\n, + "To Date:,", LastDate, $\n, $\n, + "Chat(s):,", ToUsername, $\n, $\n, + "Included:,Text", $\n, + ",Photos", $\n, + ",Locations", $\n, + ",Files", $\n, $\n, + "Save Media as:,Links", $\n, $\n, + "Date,Time,People,Message" + ]. + +-spec extract_chat_data(ChatHistory::type_chat_history(), Members::list(type_user_data())) -> type_chat(). +extract_chat_data(ChatHistory, Members) -> extract_chat_data(ChatHistory, Members, ?MIN_FROM, ?MAX_TO, []). +extract_chat_data(ChatHistory, Members, FromDate, ToDate, AllowedMessageTypes) -> + [begin + {Date, Time} = roster:msToUT(TimeCreated), + FormattedDate = format_date(Date), + FormattedTime = format_time(Time), + Username = match_member_to_phone_id(Members, PhoneId), + Message = format_message(PayloadType, Payload), + {FormattedDate, FormattedTime, Username, Message} + end + || #'Message'{created = TimeCreated, + from = PhoneId, + type = [], + files = [#'Desc'{mime = PayloadType, payload = Payload} | _]} <- ChatHistory, + TimeCreated >= FromDate, TimeCreated =< ToDate, lists:member(binary_to_list(PayloadType), AllowedMessageTypes) == true + ]. + +-spec extract_user_id(binary()) -> non_neg_integer(). +extract_user_id(PhoneId) -> + [_Phone, UserId] = string:split(PhoneId, "_"), + element(1, string:to_integer(UserId)). + +-spec match_member_to_phone_id(Members::list(type_user_data()), PhoneId::binary()) -> string(). +match_member_to_phone_id(Members, PhoneId) -> + case lists:dropwhile(fun({Id, _}) -> Id /= PhoneId end, Members) of + [] -> throw({no_username_match, PhoneId}); + [{_, Username} | _] -> Username; + _ -> throw({no_username_match, PhoneId}) + end. + +-spec format_date(tuple()) -> string(). +format_date({Year, Month, Day}) -> io_lib:format("~2..0w/~2..0w/~w", [Day, Month, Year]). + +-spec format_time(tuple()) -> string(). +format_time({Hour, Min, Sec}) -> io_lib:format("~2..0w:~2..0w:~2..0w", [Hour, Min, Sec]). + +-spec format_message(PayloadType::binary(), Payload::binary()) -> binary() | list(binary()). +format_message(<<"text">>, Payload) -> Payload; +format_message(<>, Payload) -> + [io_lib:format("~s~s: ", [string:uppercase(FirstLetter), Rest]), Payload]; +format_message(_, Payload) -> [<<"Unknown: ">>, Payload]. + +-spec get_username_by_id(Id::non_neg_integer()) -> string(). +get_username_by_id(Id) -> + case kvs:get('Roster', Id) of + {ok, #'Roster'{names = Name, surnames = Surname}} -> io_lib:format("~s ~s", [Name, Surname]); + {error, _} -> throw({no_username, Id}) + end. + +-spec get_group_chat_members(RoomId::binary()) -> list(#'Member'{}). +get_group_chat_members(RoomId) -> + case roster:members({muc, RoomId}) of + [] -> + throw({no_room, RoomId}); + Members -> + [ + {PhoneId, io_lib:format("~s ~s", [Name, Surname])} + || #'Member'{names = Name, + surnames = Surname, + phone_id = PhoneId} <- Members + ] + end. + +-spec get_group_chat_name(RoomId::binary()) -> string(). +get_group_chat_name(RoomId) -> + case kvs:get('Room', RoomId) of + {ok, #'Room'{name = RoomName}} -> RoomName; + {error, _} -> throw({no_muc_name, RoomId}) + end. + +-spec get_chat_history(Feed::tuple()) -> list(#'Message'{}). +get_chat_history(Feed) -> + case roster_db:get_chain('Message', roster:feed_key(Feed)) of + [] -> throw({no_chat_history, Feed}); + History -> History + end. + +-spec get_chat_time_span(Chat::type_chat_element()) -> {string(), string()}. +get_chat_time_span([]) -> {"None", "None"}; +get_chat_time_span(Chat) -> + [{FirstDate, _, _, _} | _] = Chat, + [{LastDate, _, _, _} | _] = lists:reverse(Chat), + {FirstDate, LastDate}. \ No newline at end of file diff --git a/apps/roster/src/rest/rest_cri.erl b/apps/roster/src/rest/rest_cri.erl new file mode 100644 index 0000000000000000000000000000000000000000..b510650772dd64210fdec2b04f26cec22ee7c35e --- /dev/null +++ b/apps/roster/src/rest/rest_cri.erl @@ -0,0 +1,354 @@ +-module(rest_cri). +-compile({parse_transform, lager_transform}). +-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 +]). + +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}} = roster_channel_helper:get_channel_by_id(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, [], []}). + +%% ------------------------------------------------------------------ +%% 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 roster_channel_helper:get_channel_by_id(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 roster_channel_helper:get_channel_by_id(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]). + +phone_id_to_member(PhoneIds) -> +%% {Members, Failed} + lists:foldl(fun(PhoneId, {Members, Failed}) -> + case roster:to_member(nitro:to_binary(PhoneId), member) of + {error, _} -> {Members, Failed ++ [PhoneId]}; + {ok, Member} -> {Members ++ [Member], Failed} + end + end, {[], []}, PhoneIds). + +failed_json(PhoneList) -> + jsx_tuple_to_json([ + {failed, iolist_to_binary( + ["[", + string:join( + [binary_to_list(jsx_tuple_to_json([{?PHONE_ID_PARAM, FN}, {error, ?ERROR_USER_404}])) || FN <- PhoneList], + ","), + "]"] + )} + ]). + +request_with_failed_status(PhoneIdList, Room) -> + {Members, FailedNumbers} = phone_id_to_member(PhoneIdList), +%% 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))}; + _ -> + 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, []} + 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; + _ -> 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; + _ -> 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 + 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 + 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; + _ -> ok + end. + +validate_permission(PhoneId, #'CallBubbleJSON'{type = CallType, feed_id = FeedId} = Data) -> + validate_permission(PhoneId, get_feed(nitro:to_binary(CallType), FeedId), Data). +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 + 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 + end. + +get_feed(?CALL_TYPE_CONFERENCE, FeedId) -> #muc{name = nitro:to_binary(FeedId)}; +get_feed(?CALL_TYPE_P2P, FeedId) -> + [From, To] = string:tokens(FeedId, "/"), + #p2p{from = nitro:to_binary(From), to = nitro:to_binary(To)}. + +create_message(FromPhoneId, #'CallBubbleJSON'{feed_id = FeedId, call_id = ConferenceId, type = CallType, + recipients = RecipientsList, duration = Duration, start_time = StartTime, status = Status, + content_type = ContentType, ended_by = EndedBy, count = Count}) -> + MsgFeedId = get_feed(nitro:to_binary(CallType), FeedId), + MsgTo = case nitro:to_binary(CallType) of + ?CALL_TYPE_CONFERENCE -> #'muc'{name = RoomId} = MsgFeedId, RoomId; + ?CALL_TYPE_P2P -> #'p2p'{from = U1, to = U2} = MsgFeedId, if FromPhoneId == U1 -> U2; true -> U1 end + end, + DescId = nitro:to_binary(kvs:next_id('Desc', 1)), + Features = [#'Feature'{id = iolist_to_binary([DescId, "_", Key]), key = Key, value = Value, + group = ?CALL_FG_CALL_DATA} || {Key, Value} <- [ + {?CALL_FK_DURATION, nitro:to_binary(Duration)}, + {?CALL_FK_START_TIME, nitro:to_binary(StartTime)}, + {?CALL_FK_CONFERENCE_ID, nitro:to_binary(ConferenceId)}, + {?CALL_FK_ENDED_BY, nitro:to_binary(EndedBy)}, + {?CALL_FK_COUNT, nitro:to_binary(Count)}]], + #'Message'{container = chain, feed_id = MsgFeedId, from = FromPhoneId, to = MsgTo, created = roster:now_msec(), + seenby = [nitro:to_binary(R) || R <- RecipientsList], msg_id = roster:generate_server_id(), + 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 diff --git a/apps/roster/src/rest/rest_deeplink.erl b/apps/roster/src/rest/rest_deeplink.erl index 1c3ff887c772579ed0fc822a750f38110ddeb42a..9037234e5391eefb1aa109f7fc1d9fc2f5621ca0 100644 --- a/apps/roster/src/rest/rest_deeplink.erl +++ b/apps/roster/src/rest/rest_deeplink.erl @@ -2,6 +2,7 @@ -compile({parse_transform, lager_transform}). -include("roster.hrl"). -include("rest_static.hrl"). +-include_lib("roster/include/static/rest_var.hrl"). -export([handle_request/3]). @@ -12,23 +13,24 @@ 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_helpers:get_query_value(QueryData, ?ROOM_LINK_KEY) of - {error, QueryParamError} -> {400, QueryParamError}; + {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 - [] -> {404, ?CHANNEL_NOT_FOUND}; + [] -> {?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}], - {200, RoomResponse} + {?HTTP_CODE_200, RoomResponse} end end, roster:info(?MODULE, "ResponseData:~p", [ResponseData]), Req:respond({ResponseStatus, [], jsx:encode([{<<"data">>, ResponseData}])}); -handle_request(Method, Path, Req) -> - roster:info(?MODULE, "MethodNotAllowed:~p:~p", [Method, Path]), - Req:respond({405, [], []}). \ No newline at end of file +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 index 4ea37bd16b9239c89701b38bce8b1eb58a883506..fa030dfa1274c244048c5b290c13ffd32bd696c8 100644 --- a/apps/roster/src/rest/rest_fake_numbers.erl +++ b/apps/roster/src/rest/rest_fake_numbers.erl @@ -1,5 +1,6 @@ -module(rest_fake_numbers). -include("roster.hrl"). +-include_lib("roster/include/static/rest_var.hrl"). -compile({parse_transform, lager_transform}). -export([handle_request/3]). @@ -8,7 +9,7 @@ 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({200, [{"Content-Type", "text/html"}], HTMLOutput}); + 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()]), @@ -25,7 +26,7 @@ handle_request('GET', "/fn", Req) -> "\\]\\\"", "\\]", [{return, list}, global]), "\\\"\\[", "\\[", [{return, list}, global]), roster:info(?MODULE, "Res:~p", [FakeNumbers]), - Req:respond({200, [], Res}); + Req:respond({?HTTP_CODE_200, [], Res}); handle_request('PUT', "/fn", Req) -> ReqBody = mochiweb_request:recv_body(Req), @@ -71,7 +72,7 @@ handle_request('PUT', "/fn", Req) -> end end), roster:info(?MODULE, "Res:~p", [Res]), - Req:respond({case string:str(binary_to_list(Res), "Error") of 0 -> 200; _ -> 400 end, [], Res}); + Req:respond({case string:str(binary_to_list(Res), "Error") of 0 -> ?HTTP_CODE_200; _ -> ?HTTP_CODE_400 end, [], Res}); handle_request('DELETE', "/fn", Req) -> QueryParams = Req:parse_qs(), @@ -92,8 +93,8 @@ handle_request('DELETE', "/fn", Req) -> "\\\"\\{", "\\{", [{return, list}, global]), "\\}\\\"", "\\}", [{return, list}, global]), roster:info(?MODULE, "Res:~p", [Res]), - Req:respond({case string:str(Res, "Error") of 0 -> 200; _ -> 400 end, [], Res}); + Req:respond({case string:str(Res, "Error") of 0 -> ?HTTP_CODE_200; _ -> ?HTTP_CODE_400 end, [], Res}); handle_request(Method, Path, Req) -> roster:info(?MODULE, "MethodNotAllowed:~p:~p", [Method, Path]), - rest_helpers:response(Req, 405, rest_helpers:error_405()). \ No newline at end of file + 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 index 7c113f8acdfc34111b43fd04d349a39e43fc910b..a985c53ef490778bff510edc7a8c08a7d7cc9e4b 100644 --- a/apps/roster/src/rest/rest_handler.erl +++ b/apps/roster/src/rest/rest_handler.erl @@ -1,6 +1,7 @@ -module(rest_handler). -compile({parse_transform, lager_transform}). --include_lib("roster/static/rest_text.hrl"). +-include_lib("roster/include/static/rest_text.hrl"). +-include_lib("roster/include/static/rest_var.hrl"). -export([init/0, handle_request/1, binary_join/2, c_tpl/0, c_tpl/1, c_tpl/2, docroot/0]). %% TODO fix with wait() @@ -14,7 +15,7 @@ init() -> mochiweb:start_http('mqtt:http', ?REST_PORT, [], {rest_handler, handle_request, []}), - roster:info(?MODULE, "REST_PID:~p", [rest_helpers:rest_pid()]), + roster:info(?MODULE, "REST_PID:~p", [rest_main_helper:rest_pid()]), c_tpl(). %% ------------------------------------------------------------------ @@ -42,8 +43,8 @@ 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). + ok = erlydtl:compile(File, re:replace(filename:basename(File), ".dtl", "_tpl", [global, {return, list}]), Opts), + c_tpl(Files, Opts). %% ------------------------------------------------------------------ %% Rest Handlers Routing @@ -59,10 +60,13 @@ handle_request(Req) -> case string:find(Path, "assets/") of nomatch -> %% /test - case lists:member(rest_helpers:bin(Req:get(path)), ?OPEN_API_LIST) of + 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_helpers:authorized(Req) of - false -> rest_helpers:response(Req, 401, rest_helpers:error_401()); + _ -> 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, "Unauthorized! Token is invalid."); + {error, token_expired} -> rest_response_helper:error_response_api(Req, ?HTTP_CODE_401, "Unauthorized! 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; @@ -77,20 +81,38 @@ handle_request(Method, "/admin_whitelist" = Path, Req) -> case ?CHECK_WHITELIST 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; -handle_request(Method, "/push/message" = Path, Req) -> rest_push :handle_request(Method, Path, Req); -handle_request(Method, "/room" = Path, Req) -> rest_deeplink :handle_request(Method, Path, Req); -handle_request(Method, "/publish" = Path, Req) -> rest_publish :handle_request(Method, Path, Req); -handle_request(Method, "/metrics" = Path, Req) -> rest_metric :handle_request(Method, Path, Req); -handle_request(Method, "/users" = Path, Req) -> rest_users:handle_request(Method, Path, Req); +%% 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_helpers:response(Req, 200, Res); + rest_response_helper:response(Req, ?HTTP_CODE_200, Res); 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_helpers:response(Req, 404, rest_helpers:error_404()). \ No newline at end of file + 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_404, "Unexpected HTTP request."). \ No newline at end of file diff --git a/apps/roster/src/rest/rest_helpers.erl b/apps/roster/src/rest/rest_helpers.erl deleted file mode 100644 index 0d99bfed76306ff9e630793dcb2933adff1c3f4c..0000000000000000000000000000000000000000 --- a/apps/roster/src/rest/rest_helpers.erl +++ /dev/null @@ -1,105 +0,0 @@ --module(rest_helpers). --include("rest_static.hrl"). --include_lib("roster/static/rest_text.hrl"). --export([description/0, - start_emqttd_process/0, rest_pid/0, authorized/1, get_query_value/2, validate_msg_to_publish/1, bin/1, - response/3, response/4, success_response/2, error_response/2, success_200/0, error_405/0, error_404/0, error_401/0]). - -description() -> "Rest Helpers Module". - --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, []))). - -start_emqttd_process() -> -%% start emqttd process and set it pid to application variables - {ok, C} = emqttc:start_link([{client_id, <<"rest">>}, {logger, {console, error}}, {reconnect, 5}]), - application:set_env(rest, rest_pid, C), - C. - -rest_pid() -> -%% get emqttd process id (pid) for mqtt publishing - case application:get_env(rest, rest_pid) of - {ok, C} -> C; - _ -> start_emqttd_process() - end. - -get_query_value(QuerySet, Key) -> - case lists:keyfind(Key, 1, QuerySet) of - false -> {error, ?QUERY_PARAM_NOT_FOUND}; - {Key, Value} -> {ok, Value} - end. - -response(Req, Status, Body) -> - response(Req, Status, Body, "application/json"). -response(Req, Status, Body, ContentType) -> -%% respond with application/json content type - Req:respond({Status, [{"Content-Type", ContentType}, {"WWW-Authenticate", "Basic realm=\"nynjaApp\""}], Body}). - -error_response([], Msg) -> - error_response(400, Msg); -error_response(Status, []) -> - error_response(Status, ?ERROR_400); -error_response(Status, Msg) -> - response_json(Status, Msg). - -success_response([], Msg) -> - success_response(200, Msg); -success_response(Status, []) -> - success_response(Status, ?RESPONSE_200); -success_response(Status, Msg) -> - response_json(Status, Msg). - -response_json(Status, Msg) -> - jsx:encode([{<<"status">>, Status}, {<<"message">>, Msg}]). - -error_404() -> - error_response(404, ?ERROR_404). - -error_405() -> - error_response(405, ?ERROR_405). - -error_401() -> - error_response(401, ?ERROR_401). - -success_200() -> - success_response([], []). - -%% TODO refactor it. Join with is_base64 fun if it is possible -validate_msg_to_publish(Data) -> -%% check is message data base64 encodded bytearray - try - binary_to_list(base64:decode(Data)) - catch E:R -> - roster:info(?MODULE, "~p:~pBase64 Decoding Error! ~p", [E, R, Data]), - false - end. - -is_base64(Data) -> - try - base64:decode(Data), - true - catch E:R -> - roster:info(?MODULE, "~p:~pBase64DecodingError! ~p", [E, R, Data]), - false - end. - -authorized(Req) -> - case Req:get_header_value("Authorization") of - "Basic " ++ BasicAuth -> - case is_base64(BasicAuth) of - true -> - case list_to_tuple(binary:split(base64:decode(BasicAuth), <<":">>)) of - {Username, Password} -> - case (bin(Username) == bin(?AUTH_USERNAME) andalso bin(Password) == bin(?AUTH_PASSWORD)) of - true -> true; - _ -> - roster:info(?MODULE, "BasicAuthFailure:Username=~s,Password=~p", [Username, Password]), - false end; - _ -> false end; - _ -> false end; - _ -> false end. - -%% TODO find to_binary function in another module and use it instead -bin(S) when is_list(S) -> list_to_binary(S); -bin(A) when is_atom(A) -> bin(atom_to_list(A)); -bin(B) when is_binary(B) -> B. \ No newline at end of file diff --git a/apps/roster/src/rest/rest_metric.erl b/apps/roster/src/rest/rest_metric.erl index 039ed426fd207ae8f49b52202be3020e0408881e..efd80c2948ba9a63cf7aa08e5dbebf756ec22ba1 100644 --- a/apps/roster/src/rest/rest_metric.erl +++ b/apps/roster/src/rest/rest_metric.erl @@ -1,7 +1,8 @@ -module(rest_metric). -compile({parse_transform, lager_transform}). -include("roster.hrl"). --include_lib("roster/static/prometheus_var.hrl"). +-include_lib("roster/include/static/prometheus_var.hrl"). +-include_lib("roster/include/static/rest_var.hrl"). -export([handle_request/3]). @@ -13,8 +14,9 @@ handle_request('GET', _, Req) -> [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_helpers:response(Req, 200, prometheus_text_format:format(), "text/plain"); + rest_response_helper:response(Req, 200, prometheus_text_format:format(), "text/plain"); -handle_request(Method, Path, Req) -> - roster:info(?MODULE, "MethodNotAllowed:~p:~p", [Method, Path]), - Req:respond({405, [], []}). \ No newline at end of file +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 index 558bebc7bed59a2e1afa9ae6607e03d287b0fe01..e4c1464705c1cb495c363af7a14193f982c368e9 100644 --- a/apps/roster/src/rest/rest_publish.erl +++ b/apps/roster/src/rest/rest_publish.erl @@ -1,7 +1,8 @@ -module(rest_publish). -compile({parse_transform, lager_transform}). -include("roster.hrl"). --include_lib("roster/static/rest_text.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". @@ -12,29 +13,30 @@ handle_request(Method, Path, Req) when Method == 'POST' -> {ResponseStatus, Response} = case jsx:is_json(ReqBody) of false -> - {400, rest_helpers:error_response(400, ?ERROR_INVALID_JSON)}; + {?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 -> - {400, rest_helpers:error_response([], ?ERROR_MISSING_PARAM)}; + {?HTTP_CODE_400, rest_response_helper:error_response(?ERROR_MISSING_PARAM)}; _ -> case lists:member(Qos, lists:seq(0, 2)) of - false -> {400, rest_helpers:error_response([], ?ERROR_INVALID_QOS_PARAM)}; + false -> {?HTTP_CODE_400, rest_response_helper:error_response(?ERROR_INVALID_QOS_PARAM)}; _ -> - case rest_helpers:validate_msg_to_publish(Msg) of - false -> {400, rest_helpers:error_response([], ?ERROR_INVALID_MESSAGE_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_helpers:rest_pid(), Topic, term_to_binary(DecodedMsg), [{qos, Qos}]), - {200, rest_helpers:success_200()} + 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_helpers:response(Req, ResponseStatus, Response); + rest_response_helper:response(Req, ResponseStatus, Response); -handle_request(Method, Path, Req) -> - roster:info(?MODULE, "MethodNotAllowed:~p:~p", [Method, Path]), - rest_helpers:response(Req, 405, rest_helpers:error_405()). \ No newline at end of file +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 index d6e7437a27995a136441bd7484b8d53ac23d6cb5..21fb0cf1c5c924b0a42f3f859d9b41b632fb717e 100644 --- a/apps/roster/src/rest/rest_push.erl +++ b/apps/roster/src/rest/rest_push.erl @@ -1,6 +1,7 @@ -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 @@ -12,7 +13,7 @@ handle_request('POST', _, Req) -> case jsx:is_json(ReqBody) of false -> roster:info(?MODULE, "Error Invalid Json", []), - 400; + ?HTTP_CODE_400; _ -> %% "payload" field is json %% 'PushServiceRest':from_json/1 method makes it erlang json ([{key, value}, ..., {keyN, valueN}]) @@ -22,14 +23,15 @@ handle_request('POST', _, Req) -> 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)], - 200 + ?HTTP_CODE_200 end, roster:info(?MODULE, "~p:~p:Response:~p", [Req:get(method), Req:get(path), ResponseStatus]), Req:respond({ResponseStatus, [], []}); -handle_request(Method, Path, Req) -> - roster:info(?MODULE, "MethodNotAllowed:~p:~p", [Method, Path]), - Req:respond({405, [], []}). +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) -> diff --git a/apps/roster/src/rest/rest_session.erl b/apps/roster/src/rest/rest_session.erl index b1d01ffc67d481385200cfbc3c82b5bfbbe12f74..22fc8704f8757243ac38de5d02713e3b6f167fdd 100644 --- a/apps/roster/src/rest/rest_session.erl +++ b/apps/roster/src/rest/rest_session.erl @@ -1,13 +1,16 @@ -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 -> {400, rest_helpers:error_response(400, <<"Empty query parameter. Please, add it in format: phone=RequestPhoneNumber">>)}; + 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 @@ -15,7 +18,7 @@ handle_request(Method, Path, Req) when Method == 'GET' -> Phone = element(2, FirstQueryParam), Sessions = kvs:index('Auth', phone, list_to_binary(Phone)), case Sessions of - [] -> {400, rest_helpers:error_response(400, <<"Cannot find sessions for requested number. Please, check input query parameters">>)}; + [] -> {?HTTP_CODE_400, rest_response_helper:error_response(<<"Cannot find sessions for requested number. Please, check input query parameters">>)}; _ -> R = iolist_to_binary([<<"[">>, rest_handler:binary_join( @@ -31,19 +34,19 @@ handle_request(Method, Path, Req) when Method == 'GET' -> end || #'Auth'{settings = FeaturesList} = S <- Sessions]), <<",">>), <<"]">>]), - {200, re:replace(re:replace(re:replace(re:replace(re:replace( + {?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; - _ -> {400, rest_helpers:error_response(400, <<"Invalid query parameter. Please, add it in format: phone=RequestPhoneNumber">>)} + _ -> {?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_helpers:response(Req, ResponseStatus, ResponseBody); + rest_response_helper:response(Req, ResponseStatus, ResponseBody); handle_request(Method, Path, Req) -> roster:info(?MODULE, "MethodNotAllowed:~p:~p", [Method, Path]), - rest_helpers:response(Req, 405, rest_helpers:error_405()). \ No newline at end of file + 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 new file mode 100644 index 0000000000000000000000000000000000000000..dede61b152f406b9ff6cdd01d79645e02c706b38 --- /dev/null +++ b/apps/roster/src/rest/rest_transcribe.erl @@ -0,0 +1,84 @@ +%%%----------------------------------------------------------------------------- +%%% @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 index 8b18b9f33e3acce4e0700b8513591b4c6d709d65..1ea97604400dcb4b07cb07d3ade3c09353987120 100644 --- a/apps/roster/src/rest/rest_users.erl +++ b/apps/roster/src/rest/rest_users.erl @@ -2,10 +2,10 @@ -compile({parse_transform, lager_transform}). -include("roster.hrl"). -include("rest_static.hrl"). --include_lib("roster/static/rest_text.hrl"). --include_lib("roster/static/room_channel_text.hrl"). --include_lib("roster/static/room_channel_var.hrl"). --include_lib("roster/static/main_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, @@ -30,9 +30,10 @@ handle_request('GET', _, Req) -> roster:info(?MODULE, "ResponseData:~p", [ResponseData]), Req:respond({ResponseStatus, [], ResponseData}); -handle_request(Method, Path, Req) -> - roster:info(?MODULE, "MethodNotAllowed:~p:~p", [Method, Path]), - Req:respond({405, [], []}). +handle_request(_, _, Req) -> + rest_response_helper:error_405_response(Req). +%% roster:info(?MODULE, "MethodNotAllowed:~p:~p", [Method, Path]), +%% Req:respond({405, [], []}). %% ------------------------------------------------------------------ %% Users Local Helpers @@ -41,7 +42,7 @@ handle_request(Method, Path, Req) -> get_ordering_param(RequestQuery) -> Param = "order", DefaultValue = <<"desc">>, - case rest_helpers:get_query_value(RequestQuery, Param) of + case rest_main_helper:get_query_value(RequestQuery, Param) of {ok, Val} when Val == "asc" -> nitro:to_binary(Val); _ -> DefaultValue end. @@ -50,7 +51,7 @@ get_size_param(RequestQuery) -> Param = "page_size", DefaultValue = 50, - case rest_helpers:get_query_value(RequestQuery, Param) of + case rest_main_helper:get_query_value(RequestQuery, Param) of {ok, Val} -> try ReqParam = binary_to_integer(nitro:to_binary(Val)), @@ -63,7 +64,7 @@ get_page_param(RequestQuery) -> Param = "page", DefaultValue = 1, - case rest_helpers:get_query_value(RequestQuery, Param) of + case rest_main_helper:get_query_value(RequestQuery, Param) of {ok, Val} -> try ReqParam = binary_to_integer(nitro:to_binary(Val)), diff --git a/apps/roster/src/rest/rest_whitelist.erl b/apps/roster/src/rest/rest_whitelist.erl index 110c48f963c0849ba2e622483807a4f40e3dda56..40a6699aff208c95c9e6203888ff6e4296c0cb69 100644 --- a/apps/roster/src/rest/rest_whitelist.erl +++ b/apps/roster/src/rest/rest_whitelist.erl @@ -1,8 +1,11 @@ -module(rest_whitelist). -include("roster.hrl"). +-include_lib("roster/include/static/rest_var.hrl"). -compile({parse_transform, lager_transform}). -export([handle_request/3]). +%% TODO refactor me! + handle_request('GET', "/whitelist", Req) -> roster:info(?MODULE, "~p:GET:~p", [Req:get(path), Req:parse_qs()]), WhitelistRes = iolist_to_binary([<<"[">>, @@ -18,7 +21,7 @@ handle_request('GET', "/whitelist", Req) -> "\\]\\\"", "\\]", [{return, list}, global]), "\\\"\\[", "\\[", [{return, list}, global]), roster:info(?MODULE, "Res:~p", [WhitelistRes]), - Req:respond({200, [], Res}); + Req:respond({?HTTP_CODE_200, [], Res}); handle_request('PUT', "/whitelist", Req) -> ReqBody = mochiweb_request:recv_body(Req), @@ -73,7 +76,7 @@ handle_request('PUT', "/whitelist", Req) -> end end), roster:info(?MODULE, "Res:~p", [Res]), - Req:respond({case string:str(binary_to_list(Res), "Error") of 0 -> 200; _ -> 400 end, [], Res}); + Req:respond({case string:str(binary_to_list(Res), "Error") of 0 -> ?HTTP_CODE_200; _ -> ?HTTP_CODE_400 end, [], Res}); handle_request('DELETE', "/whitelist", Req) -> QueryParams = Req:parse_qs(), @@ -105,8 +108,8 @@ handle_request('DELETE', "/whitelist", Req) -> "\\\"\\{", "\\{", [{return, list}, global]), "\\}\\\"", "\\}", [{return, list}, global]), roster:info(?MODULE, "Res:~p", [Res]), - Req:respond({case string:str(Res, "Error") of 0 -> 200; _ -> 400 end, [], Res}); + Req:respond({case string:str(Res, "Error") of 0 -> ?HTTP_CODE_200; _ -> ?HTTP_CODE_400 end, [], Res}); handle_request(Method, Path, Req) -> roster:info(?MODULE, "MethodNotAllowed:~p:~p", [Method, Path]), - rest_helpers:response(Req, 405, rest_helpers:error_405()). \ No newline at end of file + 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/roster.app.src b/apps/roster/src/roster.app.src index 90457426844e29c085d478a4aa946e7e935d9fb5..e5ed158381e133b9a1950c109e602c6724111cfc 100644 --- a/apps/roster/src/roster.app.src +++ b/apps/roster/src/roster.app.src @@ -2,6 +2,6 @@ [{description, "Roster Protocol"}, {vsn, "1"}, {registered, []}, - {applications, [kernel,stdlib,mnesia,n2o,kvs,bpe,locus,prometheus]}, + {applications, [kernel,stdlib,mnesia,kvs,emqttd,n2o,bpe,locus,prometheus,libphonenumber_erlang]}, {mod, { roster, []}}, {env, []} ]}. diff --git a/apps/roster/src/roster.erl b/apps/roster/src/roster.erl index b69ba66028af45708a601d527e9da0615bcd74ff..8efcefd88d27c9a2ef80da24988b140d81875471 100644 --- a/apps/roster/src/roster.erl +++ b/apps/roster/src/roster.erl @@ -10,6 +10,8 @@ -include_lib("kvs/include/group.hrl"). -include_lib("emqttd/include/emqttd.hrl"). -include_lib("mnesia/src/mnesia.hrl"). +-include_lib("roster/include/static/main_var.hrl"). +-include_lib("roster/include/static/rest_text.hrl"). -compile({parse_transform, bert_validator}). -define(BPE_SYS_CLIENT, <<"sys_bpe">>). @@ -19,19 +21,19 @@ -define(URL_RE, <<"(?:(?:https?|ftp|file|smtp):\/\/|www\.|ftp\.)(?:\([-A-Z0-9+&@#\/%=~_|$?!:,.]*\)" "|[-A-Za-z0-9+&@#\/%=~_|$?!:,.])*(?:\([-A-Za-z0-9+&@#\/%=~_|$?!:,.]*\)" "|[A-Za-z0-9+&@#\/%=~_|$])">>). - log_level() -> info. log_modules() -> [ %% roster api - roster_auth, roster_friend, roster_test, roster_message, roster_profile, roster_presence, roster_roster, roster_room, - roster_search, roster_proto, roster_bpe, roster_favorite, roster_history, roster, roster_acl, roster_push, - roster_channel, roster_channel_helper, roster_channel_history, roster_channel_member, roster_channel_link, - roster_channel_message, roster_db, roster_data, + roster_auth, roster_friend, roster_test, roster_client, roster_message, roster_profile, roster_presence, roster_roster, roster_room, + roster_search, roster_proto, roster_bpe, roster_favorite, roster_history, roster, roster_acl, roster_push, roster_link, + roster_db, roster_data, +%% micro service api + micro_test, micro_auth, micro_roster, micro_profile, %% 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_users, + rest_fake_numbers, rest_metric, rest_cri, rest_users, %% test staff main_test, auth_test, room_test, %% other staff @@ -46,16 +48,21 @@ 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(), @@ -63,25 +70,30 @@ start(_, _) -> atoms(), catch load([]), roster_push:start(), stickers_api:init_default_pack(), init_default_fake_numbers(), - libphonenumber_erlang_sup:start_link(), prometheus_api:init(), - sync_indexes(), A end catch E:Z -> + store_mnesia:sync_indexes(), A end catch E:Z -> io:format("FATAL ROSTER START: ~p~n",[{E,Z,erlang:get_stacktrace()}]), [] end, X. -stop(_) -> unload(), emqttd_access_control:unregister_mod(auth, n2o_auth), - emqttd_access_control:unregister_mod(auth, roster_auth), ok. +osd(A) -> round((os:system_time() - A)/1000). + +stop(_) -> unload(), + emqttd_access_control:unregister_mod(auth, n2o_auth), + emqttd_access_control:unregister_mod(auth, roster_auth), + emqttd_access_control:unregister_mod(auth, micro_auth), + ok. metainfo() -> #schema{name = kvs, tables = tables()}. tables() -> [ #table{name = 'Auth', fields = record_info(fields, 'Auth'), keys = [dev_key, user_id, phone, push]}, #table{name = 'Roster', fields = record_info(fields, 'Roster')}, - #table{name = 'Message', container = chain, fields = record_info(fields, 'Message'), keys = [from, to]}, + #table{name = 'Message', container = chain, fields = record_info(fields, 'Message'), keys = [from, to, msg_id]}, #table{name = chain, container = true, fields = record_info(fields, chain)}, #table{name = writer, fields = record_info(fields, writer)}, #table{name = reader, fields = record_info(fields, reader)}, #table{name = 'Room', fields = record_info(fields, 'Room')}, + #table{name = 'Link', fields = record_info(fields, 'Link')}, #table{name = 'Member', container = chain, fields = record_info(fields, 'Member')}, #table{name = 'Profile', fields = record_info(fields, 'Profile')}, #table{name = 'Index', fields = record_info(fields, 'Index')}, @@ -103,12 +115,17 @@ 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 n2o_vnode:unsubscribe(ClientId, roster:action_topic(ClientId)), + kvs:delete('Auth', ClientId) end || #'Auth'{client_id = ClientId} <- kvs:index('Auth', Index, P)]. + delete_sessions({client_id, ClientId}) -> kvs:delete('Auth', ClientId); delete_sessions(Phone) -> - [kvs:delete('Auth', ClientId) || #'Auth'{client_id = ClientId} <- kvs:index('Auth', phone, Phone)]. + delete_sessions(phone, Phone). find_sessions(Phone) -> [ClientId || #'Auth'{client_id = ClientId} <- kvs:index('Auth', phone, Phone)]. @@ -116,7 +133,7 @@ find_sessions(Phone) -> full_purge_user(Phone) -> purge_user(Phone, full_remove_roster). purge_user(Phone) -> purge_user(Phone, remove_roster). -purge_user(Phone, Fun) -> +purge_user(Phone, Fun) when is_atom(Fun) -> info(?MODULE, "purge_user::~p", [Phone]), R = case kvs:get('Profile', Phone) of {ok, #'Profile'{rosters = Accs}} -> @@ -133,11 +150,33 @@ list_rosters(Phone, Fun) -> {error, _} -> {error, profile_not_found}; {ok, #'Profile'{rosters = Rosters}} -> ?MODULE:Fun(Phone, Rosters) end. -is_online(P) -> - case [ok || #'Auth'{client_id = ClientId} <- kvs:index('Auth', phone, phone(P)), + +send_profile(#'Profile'{rosters = []}, _N, _LastSync, _ClientId, _C) -> ok; +send_profile(#'Profile'{rosters = [RosterId|TRosterIds], phone = Phone, status = Status} = P, N, LastSync, ClientId, C) -> + case kvs:get('Roster', RosterId) of + {error, _} -> roster:error(?MODULE, "roster ~p not found in profile ~p", [RosterId, Phone]); + {ok, #'Roster'{} = R} -> + Rosters = split_roster(R, N, LastSync, []), + [roster:send_action(C, ClientId, P#'Profile'{rosters = [Roster]}) || Roster<-Rosters], + send_profile(P#'Profile'{rosters = TRosterIds, status = Status}, N, LastSync, ClientId, C) + end. + +split_roster(#'Roster'{} = R, N, LastSync, Rosters) -> + case roster:roster(R, N, LastSync) of + #'Roster'{userlist = {[], _} , roomlist = {[], _} , favorite = {[], _}} -> Rosters; + #'Roster'{userlist = {Users, TUsers}, roomlist = {Rooms, TRooms}, favorite = {Stars, TStars}} = Roster -> + split_roster(R#'Roster'{userlist = TUsers, roomlist = TRooms, favorite = TStars}, N, LastSync, + Rosters++[Roster#'Roster'{userlist = Users, roomlist = Rooms, favorite = Stars}]) + end. + +is_online(Index, P) -> + case [ok || #'Auth'{client_id = ClientId} <- kvs:index('Auth',Index,P), emqttd_cm:lookup_proc(ClientId) /= undefined] of [] -> offline;_ -> online end. +is_online(P) -> + is_online(phone, phone(P)). + is_online2(P) -> case kvs:get('Profile', phone(P)) of {ok, #'Profile'{presence = Presence, update = Update}} -> {Presence, Update}; @@ -147,7 +186,7 @@ parts_phone_id(PhoneId) -> case string:tokens(nitro:to_list(PhoneId), "_") of [Phone, Id] -> [list_to_binary(Phone), list_to_integer(Id)]; [Phone] -> list_rosters(list_to_binary(Phone), first_roster); - _E -> info(?MODULE, "INVALID PHONE ID", []), [<<>>, 0] end. + _E -> [<<>>, 0] end. roster_id(<<"emqttd_", _/binary>> = ClientId) -> {ok, #'Auth'{user_id = PhoneId}} = kvs:get('Auth', ClientId), roster_id(PhoneId); roster_id(PhoneId) -> [_, Id] = parts_phone_id(PhoneId), Id. @@ -172,7 +211,7 @@ phone_id(<<"emqttd_", _/binary>> = ClientId) -> {ok, #'Auth'{user_id = PhoneId}} = kvs:get('Auth', ClientId), PhoneId; phone_id(P) -> roster:phone_id(roster:parts_phone_id(P)). -phone_id(?BPE_SYS_CLIENT, PhoneId) -> PhoneId; +phone_id(<<"sys_" , _/binary>>, PhoneId) -> PhoneId; phone_id(<<"emqttd_", _/binary>> = ClientId, []) -> phone_id(ClientId); phone_id(<<"emqttd_", _/binary>> = ClientId, PhoneId) -> case phone_id(ClientId) of PhoneId -> PhoneId; _-> <<>> end; @@ -225,7 +264,7 @@ remove_sessions(Phone) -> remove_sessions(kvs:index('Auth', phone, Phone)). remove_sessions(Phone, RosterId) -> remove_sessions(kvs:index('Auth', user_id, phone_id(Phone, RosterId))). remove_session(#'Auth'{client_id = ClientId}) -> - n2o:unsubscribe(ClientId, roster:action_topic(ClientId)), + n2o_vnode:unsubscribe(ClientId, roster:action_topic(ClientId)), case kvs:get(mqtt_session, ClientId) of {ok, #mqtt_session{sess_pid = SessionPid}} -> emqttd_session:destroy(SessionPid, ClientId); @@ -244,7 +283,7 @@ remove_roster(Phone, RosterId) -> kvs:delete(reader, R), remove_contacts(Id, [PId]) end || #'Contact'{phone_id = PhoneId, reader = R} <- get_contacts(RosterId)], {ok, #'Roster'{nick = Nick}} = kvs:get('Roster', RosterId), - kvs:delete('Index', {nick, string:lowercase(Nick)}), + kvs:delete('Index', {nick, list_to_binary(string:to_lower(nitro:to_list(Nick)))}), kvs:delete('Roster', RosterId), %% remove_sessions(Phone, RosterId), %% TODO temporary removed kvs:put(Profile#'Profile'{rosters = Roster, update = now_msec()}), @@ -256,41 +295,6 @@ remove_roster(Phone, RosterId) -> end end. -% STARRED MESSAGES MANAGEMENT - -get_extended_star(Local, StMsgsList) -> - lists:flatten([begin - From = case MsgFeed of -%% get room and From room member - {muc, RoomId} -> -%% protect room deletion - {Room, Unread, LastMsg} = - case kvs:get('Room', RoomId) of - {ok, FoundRoom} -> - {UnreadCalc, LastMsgCalc, _} = get_unread_msg(muc, Local, RoomId), - {FoundRoom, UnreadCalc, LastMsgCalc}; - {error, _} -> - {#'Room'{name = <<"Deleted Room">>, id = RoomId}, [], []} - end, - Room#'Room'{unread = Unread, last_msg = LastMsg, members = [ - case muc_member(MsgFrom, RoomId) of - [] -> #'Member'{alias = <<"Deleted Account">>}; - Member -> Member end]}; - _ -> - {Unread, LastMsg, _} = get_unread_msg(p2p, Local, MsgFrom), -%% get From in requested roster contacts - {Presence, Update} = is_online2(MsgFrom), - case get_contact(roster_id(Local), MsgFrom) of - {error, _} -> #'Contact'{unread = Unread, last_msg = LastMsg, - names = <<"Deleted">>, surnames = <<"Account">>, status = deleted}; - Contact -> Contact#'Contact'{unread = Unread, last_msg = LastMsg, - presence = Presence, update = Update, - reader = p2p_readmsgs(Local, MsgFrom)} - end - end, - #'ExtendedStar'{star = StMsg#'Star'{message = Msg#'Message'{status = []}}, from = From} - end || #'Star'{message = #'Message'{feed_id = MsgFeed, from = MsgFrom} = Msg} = StMsg <- StMsgsList]). - % CONTACT MANAGEMENT patch_contact(#'Contact'{} = OldC, #'Contact'{} = NewC) -> @@ -300,7 +304,7 @@ get_contacts(#'Roster'{userlist = List}, Fun) -> [Contact || #'Contact'{} = Contact <- List, Fun(Contact)]; get_contacts(RosterId, Fun) -> case kvs:get('Roster', RosterId) of - {error, _} -> {error, roster_not_found}; + {error, _} -> []; {ok, #'Roster'{} = R} -> get_contacts(R, Fun) end. @@ -441,7 +445,7 @@ on_disconnect(<<"emqttd_", _/binary>> = ClientId) -> ok. final_disconnect(ClientId) -> % info(roster_auth, "~p:ANY DISCONNECT",[RegClientId]), - n2o:unsubscribe(ClientId, roster:action_topic(ClientId)), + n2o_vnode:unsubscribe(ClientId, roster:action_topic(ClientId)), case kvs:get(mqtt_session, ClientId) of {ok, #mqtt_session{sess_pid = SessionPid}} -> emqttd_session:destroy(SessionPid, ClientId), @@ -451,8 +455,8 @@ final_disconnect(ClientId) -> on_session_terminated(_ClientId, _Username, _Reason, _Env) -> ok. - % SESSION MANAGEMENT + force_logout(#'Auth'{client_id = ClientId} = Auth) -> kvs:put(Auth#'Auth'{type = logout}), on_disconnect(ClientId); @@ -471,6 +475,8 @@ action_topic(ClientId) -> action_topic(ClientId, "1"). action_topic(ClientId, VSN) -> iolist_to_binary(["actions/", VSN, "/api/", ClientId]). send_auth(C, ClientId, Term) -> n2o_vnode:send(C, auth_topic(ClientId), term_to_binary(Term)). send_event(C, ClientId, Token, Term) -> send_event(C, ClientId, Token, Term, <<>>). +send_event(C, ClientId, Token, Term, {K,V}) -> + n2o_vnode:send(C, event_topic(ClientId, Token, <<>>), term_to_binary(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"). @@ -509,6 +515,22 @@ delete_msg(PhoneId) -> [kvs:remove('Message', ID) || #'Message'{feed_id = #p2p{}, id = ID} <- kvs:index('Message', to, PhoneId)], ok. +update_writer(Feed,{E1,V1},{E2,V2})-> + case kvs_stream:load_writer(Feed) of + #writer{cache =[_|_]= M}=W -> + case element(E1,M)==V1 of + true -> M1=erlang:setelement(E2,M,V2), + kvs_stream:save(W#writer{cache = M1}); + false -> skip end; + _ -> skip end. +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}) -> +%% update_writer(Feed,{E1,V1},{E2,V2}), +%% [kvs:remove('Message', ID) || #'Message'{feed_id = #p2p{}, id = ID} <- kvs:index(Table, Index, PhoneId)], +%% ok. + del_jobs(Name, PhoneId) -> case kvs_stream:load_writer(#act{name = Name, data = PhoneId}) of #writer{count = Top, first = #'Job'{id = Id, context = R}} -> @@ -535,9 +557,11 @@ unread_msg(#writer{cache = #'Message'{id = MaxReadId, type = [sys|_]} = LastMsg} #reader{cache = {'Message', ReadMsgId}, pos = 0} = Reader, #'Member'{id = UID, status = removed}) when ReadMsgId /= [], ReadMsgId >= MaxReadId -> - kvs:put(LastMsg#'Message'{seenby = []}), %%TODO remove this shit in future - Res = unread_msg(Writer#writer{cache = LastMsg}, Reader#reader{cache = {'Message', MaxReadId}}, UID), - kvs:put(LastMsg), Res; + GetFun = + fun (_T, MaxReadId, _D) -> {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); unread_msg(#writer{} = Writer, #reader{} = Reader, #'Member'{id = UID}) -> unread_msg(Writer, Reader, UID); unread_msg(LM, ReaderId, UID) when is_integer(ReaderId) -> @@ -551,10 +575,23 @@ unread_msg(#'Message'{} = LMsg, #reader{feed = Feed} = Reader, UID) -> unread_msg(#writer{cache = #'Message'{}} = Writer, #reader{cache = []} = Reader, UID) -> unread_msg(Writer, Reader#reader{cache = {'Message', []}}, UID); unread_msg(#writer{cache = []}, _Reader, _UID) -> {0, <<>>, []}; -unread_msg(#writer{cache = #'Message'{id = MaxReadId}} = Writer, #reader{cache = {'Message', ReadMsgId}, pos = 0} = Reader, UID) when ReadMsgId /= [], ReadMsgId > MaxReadId -> +unread_msg(#writer{cache = #'Message'{id = MaxReadId}} = Writer, + #reader{cache = {'Message', ReadMsgId}, pos = 0} = Reader, UID) + when ReadMsgId /= [], ReadMsgId > MaxReadId -> {ok, LastMsg} = kvs:get('Message', ReadMsgId), unread_msg(Writer#writer{cache = LastMsg}, Reader, UID); -unread_msg(#writer{cache = #'Message'{id = MaxReadId}, count = Count}, #reader{cache = {'Message', ReadMsgId}, pos = ReadCurPos} = Reader, UID) -> +unread_msg(#writer{cache = #'Message'{}} = Writer, + #reader{cache = {'Message', _ReadMsgId}} = Reader, UID) -> + unread_msg(Writer, Reader, UID, fun kvs:get/3); +unread_msg(Feed, #reader{} = Reader, UID) when is_record(Feed, muc); is_record(Feed, p2p) -> + 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}]), + {0, [], []}. + +unread_msg(#writer{cache = #'Message'{id = MaxReadId}, count = Count}, + #reader{cache = {'Message', ReadMsgId}, pos = ReadCurPos} = Reader, UID, GetFun) -> AccFun = fun(IsActMsg, #'Message'{id = MsgId, mentioned = Mentioned} = Msg, {Unread, LM, Mention}, _Dir) -> {Unread + case MsgId =< ReadMsgId andalso ReadMsgId /= [] of true -> 0; _-> IsActMsg end, @@ -564,8 +601,8 @@ unread_msg(#writer{cache = #'Message'{id = MaxReadId}, count = Count}, #reader{c {update, 1} -> Msg#'Message'{status = update}; {update, 0} -> update; {#'Message'{}, _} -> LM end, - case MsgId > ReadMsgId andalso lists:member(UID, Mentioned) - andalso 1 == IsActMsg of + case (MsgId > ReadMsgId orelse ReadMsgId == []) andalso lists:member(UID, Mentioned) + andalso 1 == IsActMsg of true -> [MsgId|Mention]; _-> Mention end}; (_, _, Acc, _) -> Acc end, @@ -574,30 +611,14 @@ unread_msg(#writer{cache = #'Message'{id = MaxReadId}, count = Count}, #reader{c StopFun = roster:msg_stop_fun(MaxReadId, UID), StopFun2 = fun(#'Message'{} = Msg, {{_, LM, _}, _} = A) when LM == [];LM == update -> StopFun(Msg, A); - (#'Message'{id = MsgId}, {{_, #'Message'{}, _}, #reader{pos = Pos}}) - when ReadMsgId /= [], MsgId =< ReadMsgId, ReadCurPos >= Pos -> 0; - (Msg, A) -> StopFun(Msg, A) + (#'Message'{id = MsgId}, {{_, #'Message'{}, _}, #reader{pos = Pos}}) + when ReadMsgId /= [], MsgId =< ReadMsgId, ReadCurPos >= Pos -> 0; + (Msg, A) -> StopFun(Msg, A) end, {{Unread, LastMsg, Mentions}, _} = roster:fold(FilterFun, {{0, [], []}, Reader}, 'Message', MaxReadId, [], - #kvs{mod = store_mnesia}, #iterator.next, StopFun2), - {Unread, wrap_msg(LastMsg), Mentions}; -unread_msg(Feed, #reader{} = Reader, UID) when is_record(Feed, muc); is_record(Feed, p2p) -> - unread_msg(kvs_stream:load_writer(Feed), Reader, UID); -unread_msg(F, R, U) -> info(?MODULE, "unread_msg2: unexpected data format: ~p", [{F, R, U}]), - {0, [], []}. - -%% Universal method for getting actual unread messages count and last message -get_unread_msg(muc, PhoneId, OpponentId) -> - #'Member'{id = MemId, reader = Reader} = muc_member(PhoneId, OpponentId), - unread_msg({'Message', []}, Reader, MemId); -get_unread_msg(p2p, PhoneId, OpponentId) -> - RosterId = roster_id(PhoneId), - case get_contact(RosterId, OpponentId) of - {error, _} -> {0, [], []}; - #'Contact'{reader = Reader} -> - unread_msg(feed_key(p2p, PhoneId, OpponentId), Reader, RosterId) - end. + #kvs{mod = store_mnesia}, #iterator.next, StopFun2, GetFun), + {Unread, wrap_msg(LastMsg), Mentions}. link_msg(#'Message'{feed_id = Feed, type = [reply|_], link = LinkId}, UID) when is_integer(LinkId) -> case kvs:get('Message', LinkId) of @@ -612,6 +633,7 @@ link_msg(#'Message'{feed_id = Feed, type = [reply|_], link = LinkId}, UID) when _ -> #error{code = invalid_data} end; link_msg(#'Message'{link = LinkId}, _UID) -> LinkId. +wrap_msg(#'Message'{status = clear} = Msg) -> Msg#'Message'{next = []}; wrap_msg(#'Message'{type = [reply|_], link = LinkId} = Msg) when is_integer(LinkId) -> case kvs:get('Message', LinkId) of {ok, LinkMsg} -> Msg#'Message'{link = LinkMsg}; @@ -640,7 +662,7 @@ offset_reader(Writer, #reader{} = Reader) -> offset_reader(kvs_stream:load_writer(Writer), Reader). unsubs_p2p(ClientId, RosterId, Accounts) -> - [n2o:unsubscribe(ClientId, Topic) || Account <- Accounts, + [n2o_vnode:unsubscribe(ClientId, Topic) || Account <- Accounts, Topic <- [p2p_topic(Account, RosterId), ac_topic(Account)]]. unsubscribe_p2p(<<"reg_", _/binary>>, _RosterId) -> ok; @@ -649,7 +671,7 @@ unsubscribe_p2p(<<"emqttd_", _/binary>> = ClientId, RosterId) -> {ok, #'Roster'{phone = Phone}} -> case ContactIds = [CId || #'Contact'{phone_id = CId} <- get_contacts(RosterId)] of [_ | _] -> PhoneId = phone_id(Phone, RosterId), - n2o:unsubscribe(ClientId, ses_topic(Phone)), + n2o_vnode:unsubscribe(ClientId, ses_topic(Phone)), unsubs_p2p(ClientId, PhoneId, ContactIds), ok; E -> E end; {error, _} = E -> E end; @@ -661,10 +683,10 @@ unsubscribe_p2p(Phone, RosterId) -> case ContactIds = [CId || #'Contact'{phone_id = CId} <- get_contacts(RosterId)] of [_ | _] -> PhoneId = phone_id(Phone, RosterId), - [begin n2o:unsubscribe(FClId, ac_topic(PhoneId)), - [n2o:unsubscribe(FClId, p2p_topic(CId, PhoneId)) || CId <- ContactIds] + [begin n2o_vnode:unsubscribe(FClId, ac_topic(PhoneId)), + [n2o_vnode:unsubscribe(FClId, p2p_topic(CId, PhoneId)) || CId <- ContactIds] end || FClId <- emqttd_pubsub:subscribers(ac_topic(PhoneId))], - [begin n2o:unsubscribe(ClientId, SesTopic), + [begin n2o_vnode:unsubscribe(ClientId, SesTopic), unsubs_p2p(ClientId, PhoneId, ContactIds) end || ClientId <- ClientIds], ok; E -> E end end. @@ -674,7 +696,7 @@ sub_client(Subscr, ClientId, PhoneId) when Subscr == subscribe; Subscr == unsubs [Phone, Id] = parts_phone_id(PhoneId), Topics = [[ac_topic(Friend), p2p_topic(Friend, PhoneId)] || #'Contact'{phone_id = Friend} <- get_on_status(Id, friend), Friend =/= PhoneId], - [n2o:Subscr(ClientId, Topic) || + [n2o_vnode:Subscr(ClientId, Topic) || Topic <- [ses_topic(Phone), p2p_topic(PhoneId, PhoneId) | lists:flatten(Topics)]], sub_room(Subscr, PhoneId), [sub_muc(Subscr, PhoneId, #muc{name = Room}, [ClientId]) || @@ -685,7 +707,7 @@ feed_key(#p2p{from = From, to = To}) -> feed_key(p2p, From, To); feed_key(#muc{} = Feed) -> Feed. ttl() -> application:get_env(roster, auth_ttl, 60 * 15). -depicle(Token) -> n2o_auth:bin_to_term(n2o_secret:depickle(Token)). +depicle(Token) -> case n2o_secret:depickle(Token) of <<>> -> <<>>; T -> binary_to_term(T,[safe]) end. gen_token([], Data) -> {'Token', n2o_secret:pickle(term_to_binary({now_msec() + ttl() * 1000, Data}))}; gen_token(Token, Data) -> @@ -708,27 +730,85 @@ parse_token2(Token) -> {_, D} -> D end. get_vnode(Term) -> [H|_] = binary_to_list(erlang:md5(term_to_binary(Term))), - Pos=H rem n2o:ring_max() + 1, + Pos=H rem (length(n2o:ring())) + 1, Node=case nodes() of [] -> node(); [N|_]-> N end, iolist_to_binary([lists:flatten([io_lib:format("~2.16.0b",[X]) || <> <= list_to_binary(atom_to_list(Node)++"_"++integer_to_list(Pos))])]). get_vnode(ClientId, Payload) when is_binary(Payload) ->try get_vnode(ClientId, binary_to_term(Payload)) catch _:_ -> - get_vnode(Payload) end; + n2o_vnode:get_vnode(ClientId) end; get_vnode(ClientId, #'Message'{feed_id = [], from = From, to = To} = Msg) -> get_vnode(ClientId, Msg#'Message'{feed_id = feed_key(p2p, From, To)}); %% TODO by default p2p but feed_id must be set by the client -get_vnode(_, #'Message'{feed_id = FeedId}) -> n2o:get_vnode(term_to_binary(FeedId)); -get_vnode(ClientId, _) -> n2o:get_vnode(ClientId). +get_vnode(_, #'Message'{feed_id = FeedId}) -> n2o_vnode:get_vnode(term_to_binary(FeedId)); +get_vnode(ClientId, _) -> n2o_vnode:get_vnode(ClientId). + +%% validate API +validate(#'Message'{mentioned = [_|_] = Mentioned} = Msg) -> + case [M||M<-Mentioned, not is_integer(M)] of [] -> validate(Msg#'Message'{mentioned = []}); _ -> [mentioned] end; +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}] + end; +validate(#'Message'{status = [], type = [sys|_]} = Msg) -> + info(?MODULE, "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}] + end; +validate(#'Desc'{id = <<>>} = Desc) -> [{id, Desc}]; +validate(_) -> []. + +%%if History get then message is not verified +prevalidate(#'History'{status = get, data = [#'Message'{}]} = H) -> H#'History'{data = []}; +prevalidate(D) -> D. + +validate(Payload, ClientId) -> + Term = prevalidate(binary_to_term(Payload)), + case roster_validator:validate(Term) of + [] -> ok; + Res -> info(?MODULE, "validate error:~n~p", [Res]), + emqttd:publish(emqttd_message:make( + ClientId, 2, action_topic(ClientId), + term_to_binary(#errors{code = [<<"666">>], data = Res}))), error + end. %% MUC API purge_room(false) -> []; purge_room(Room) -> + roster_link:purge_link(Room), [begin kvs:remove('Member', Id), kvs:delete(reader, R), unsubscribe_room(M), remove_rooms(PhoneId, [Room]) end || #'Member'{id = Id, phone_id = PhoneId, reader = R} = M <- members(#muc{name = Room})], kvs:delete(writer, {muc, Room}), delete_msg(#muc{name = Room}), kvs:delete('Room', Room), kvs:delete(chain, #muc{name = Room}). + +% MEMBERS MANAGEMENT +to_member(Req) -> + to_member(Req, member). +to_member(PhoneId, Status) when is_binary(PhoneId) -> +%% check is this user exists + case roster:parts_phone_id(PhoneId) of + R = {error, _} -> R; + [PhoneNumber, RosterId] -> +%% NOTE! funny crutch for cases when roster:parts_phone_id/1 returns two values on request when input data dont contains underscore + case iolist_to_binary([PhoneNumber, <<"_">>, nitro:to_binary(RosterId)]) of + PhoneId -> + case kvs:get('Roster', RosterId) of + R2 = {error, _} -> R2; +%% NOTE! One more crutch: If someone send PhoneId = PhoneA_RosterB user with RosterB will be returned, but it have to be error because PhoneId compose data of different users + {ok, #'Roster'{phone = PN} = Roster} -> + if PN == PhoneNumber -> to_member(Roster, Status); true -> {error, invalid_input_data} end + end; + _ -> {error, invalid_input_data} + end + end; +to_member(#'Roster'{id = Id, phone = Phone, nick = Nick, names = Names, surnames = Surnames, avatar = Avatar}, Status) -> + {ok, #'Member'{container = chain, alias = Nick, names = Names, surnames = Surnames, avatar = Avatar, status = Status, + phone_id = roster:phone_id(Phone, Id)}}. + remove_member(PhoneId) -> [begin kvs:remove('Member', Id), remove_rooms(PhoneId, [Room]), kvs:delete(reader, R), unsubscribe_room(M) end || #'Member'{id = Id, feed_id = #muc{name = Room}, phone_id = PhoneId2, reader = R} = M <- kvs:all('Member'), PhoneId == PhoneId2]. @@ -742,6 +822,7 @@ members(#muc{} = Feed, active) -> members(#muc{} = Feed, admin) -> [M || #'Member'{status = Status} = M <- members(Feed), lists:member(Status, [admin, owner])]. +muc_member([], _Room) -> []; muc_member(<<"emqttd_", _/binary>> = ClientId, Room) -> muc_member(phone_id(ClientId), Room); muc_member(PhoneId, Room) -> case lists:keyfind(PhoneId, #'Member'.phone_id, members(#muc{name = Room})) of @@ -924,14 +1005,77 @@ last_rooms(Rooms, N) -> end, [], Rooms), lists:sublist(lists:reverse(Rooms2), N). - -userlist(#'Roster'{userlist = List, id = Id}=Roster) -> - [user(Roster, C)|| C = #'Contact'{} <- List]; -userlist(Id) -> case kvs:get('Roster', Id) of {ok, R} -> userlist(R); _ -> [] end. +feed(#'Room'{id = Room} , _) -> #muc{name = Room}; +feed(#'Contact'{phone_id = P} , PhoneId) -> feed_key(#p2p{from = P, to = PhoneId}); +feed(#'Star'{message = #'Message'{feed_id = Feed}}, _) -> Feed. + +last_upd(#'Room'{id = Room}) -> + {ok, #'Room'{update = Update}} = kvs:get('Room', Room), Update; %% get update field from Room table! +last_upd(#'Contact'{phone_id = PhoneId, update = Update}) -> + case roster:is_online2(phone(PhoneId)) of {online, _} -> []; _-> Update end; +last_upd(#'Star'{message = #'Message'{created = Update}}) -> Update. + +last_objs(Objs, PhoneId) -> last_objs(Objs, PhoneId, 0, 100). +last_objs(Objs, PhoneId, LastSync, N) -> last_objs(Objs, PhoneId, LastSync, N, {[], 0}). +last_objs([], _PhoneId, _LastSync, _N, {Acc, MaxUpd}) -> {Acc, MaxUpd, []}; +last_objs(Objs, _PhoneId, _LastSync, N, {Acc, MaxUpd}) when length(Acc) >= N -> {Acc, MaxUpd, Objs}; +last_objs([Obj|TObjs], PhoneId, LastSync, N, {Acc, MaxUpd}) -> + {Created, W} = + case kvs_stream:load_writer(Feed = feed(Obj, PhoneId)) of + #writer{cache = #'Message'{created = Crtd}} = Writer -> {Crtd, Writer}; + #writer{} = Writer -> {0, Writer}; + {error, not_found} -> {0, Feed} + end, + LastUpd = erlang:max(Created, last_upd(Obj)), + Acc2 = case LastUpd > LastSync of true -> Acc++[{Obj, W}]; _ -> Acc end, %% TODO sort objects if update? + last_objs(TObjs, PhoneId, LastSync, N, {Acc2, erlang:max(MaxUpd, LastUpd)}). + + +split_objlist(Index, Roster) -> split_objlist(Index, Roster, 0, []). +split_objlist(Index, #'Roster'{}=Roster, LastSync, N) -> + split_objlist(element(Index, Roster), Roster, LastSync, N, []); +split_objlist(Index, Roster, LastSync, N) -> split_objlist(Index, Roster, LastSync, N, []). +split_objlist(Index, #'Roster'{} = Roster, LastSync, N, Acc) + when Index == #'Roster'.userlist; Index == #'Roster'.roomlist; Index == #'Roster'.favorite -> + 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} = objlist(List, Roster, LastSync, N), + split_objlist(TObjs, Roster, LastSync, N, Acc++[Objs]); +split_objlist(Index, Id, LastSync, N, Acc) -> + case kvs:get('Roster', Id) of {ok, R} -> split_objlist(Index, R, LastSync, N, Acc); _ -> {error, roster_not_found} end. + +objlist(Index, Roster) -> + objlist(Index, Roster, 0). +objlist(Index, Roster, LastSync) -> + objlist(Index, Roster, LastSync, []). +objlist([], _Roster, _LastSync, _N) -> {[], []}; +objlist(Index, #'Roster'{}=Roster, LastSync, N) + when Index == #'Roster'.userlist; Index == #'Roster'.roomlist; Index == #'Roster'.favorite -> + objlist(element(Index, Roster), Roster, LastSync, N); +objlist([Obj|_] = List, #'Roster'{id = Id, phone = Phone}=Roster, LastSync, N) + when is_record(Obj, 'Contact'); is_record(Obj, 'Star'); is_record(Obj, 'Room') -> + {SubList, _, TList} = last_objs(List, phone_id(Phone, Id), LastSync, N), + Objs = [case Obj of + #'Contact'{} -> user(Roster, Obj, W, LastSync); + #'Room'{} -> room(Roster, Obj, W, LastSync); + #'Star'{} -> star(Roster, Obj, W, LastSync) + end || {Obj, W} <- SubList], + {Objs, TList}; +objlist(Index, Id, LastSync, N) -> + case kvs:get('Roster', Id) of {ok, R} -> objlist(Index, R, LastSync, N); _ -> {error, roster_not_found} end. + +userlist(R) -> hd(split_objlist(#'Roster'.userlist, R)). + +user(Roster, C) -> user(Roster, C, 0). +user(RosterId, UserId, LastSync) -> + user(RosterId, UserId, [], LastSync). user(#'Roster'{id = Id, phone = Phone}, - #'Contact'{phone_id = PhoneId, reader = Reader} = C) -> + #'Contact'{phone_id = PhoneId, reader = Reader} = C, W, _LastSync) + when is_record(W, writer); is_record(W, p2p); W == [] -> + Writer = case W of [] -> kvs_stream:load_writer(feed(C, phone_id(Phone, Id))); _ -> W end, Local = phone_id(Phone, Id), - {Unread, LastMsg, _} = unread_msg(feed_key(p2p, Local, PhoneId), Reader, Local), + {Unread, LastMsg, _} = unread_msg(Writer, Reader, Local), {Presence, Update} = is_online2(PhoneId), Msgs = case catch p2p_readmsgs(Local, PhoneId) of {'EXIT', {Err, _}} -> @@ -940,14 +1084,14 @@ user(#'Roster'{id = Id, phone = Phone}, Ms -> Ms end, C#'Contact'{presence = Presence, update = Update, services = get_services(phone(PhoneId)), unread = Unread, last_msg = LastMsg, reader = Msgs}; -user(#'Roster'{id = RosterId, userlist = Users}=R, UserId) -> +user(#'Roster'{id = RosterId, userlist = Users, phone = Phone}=R, UserId, W, LastSync) -> case lists:keyfind(UserId, #'Contact'.phone_id, Users) of - #'Contact'{} = C -> user(R, C); - false -> info(?MODULE, "user ~p not found in userlist ~p", [UserId, RosterId]), [] end; -user(RosterId, UserId) -> + #'Contact'{} = C -> user(R, C, W, LastSync); + false -> info(?MODULE, "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); + {ok, #'Roster'{} = R} -> user(R, UserId, W, LastSync); {error, _} -> info(?MODULE, "roster ~p not found", [RosterId]), [] end. roomlist(Id) when is_integer(Id) -> roomlist(Id, []). @@ -956,11 +1100,15 @@ roomlist(#'Roster'{roomlist = Rooms} = Roster, N) -> case N of [] -> Rooms;_ -> last_rooms(Rooms, ?MAX_LAST_ROOMS) end]); roomlist(Id, N) -> case kvs:get('Roster', Id) of {ok, R} -> roomlist(R, N); _ -> [] end. -room(#'Roster'{id = Id, phone = Phone}, #'Room'{id = Name, last_msg = LMId} = Room) -> +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} = Room, + #writer{} = W, _LastSync) -> Local = phone_id(Phone, Id), case muc_member(Local, Name) of #'Member'{id = UID, reader = Reader} = Member when Reader > 0 -> - Rec = case is_integer(LMId) of true -> {'Message', LMId};_ -> #muc{name = Name} end, + Rec = case is_integer(LMId) of true -> {'Message', LMId};_ -> W end, %% TODO replace with code below after rewriting unread_msg %% TODO now update status may be calculated without while and status_msg %% Rec = case is_integer(LMId) of true -> {'Message', LMId}; @@ -993,31 +1141,77 @@ room(#'Roster'{id = Id, phone = Phone}, #'Room'{id = Name, last_msg = LMId} = Ro Room#'Room'{unread = Unread, last_msg = LastMsg, mentions = Mentions, admins = Admins, members = Members2, readers = Readers}); _ -> [] end; -room(#'Roster'{id = RosterId, roomlist = Rooms} = R, RoomId) -> +room(#'Roster'{id = RosterId, roomlist = Rooms} = R, RoomId, W, LastSync) -> case lists:keyfind(RoomId, #'Room'.id, Rooms) of - #'Room'{} = Room -> room(R, Room); + #'Room'{} = Room -> room(R, Room, W, LastSync); false -> info(?MODULE, "room ~p not found in roomlist ~p", [RoomId, RosterId]), [] end; -room(RosterId, RoomId) -> +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); + {ok, #'Roster'{} = R} -> room(R, RoomId, W, LastSync); {error, _} -> info(?MODULE, "roster ~p not found", [RosterId]), [] end. -roster(Id) when is_integer(Id) -> roster(Id, []). -roster(Id, N) when is_integer(Id) -> case kvs:get('Roster', Id) of {ok, R} -> roster(R, N); _ -> [] end; -roster(#'Roster'{favorite = StrMsg, id = Id, phone = Phone} = R, N) -> - Local = phone_id(Phone, Id), - R#'Roster'{userlist = userlist(R), roomlist = roomlist(R, N), - favorite = get_extended_star(Local,%% get N recent starred messages - lists:sublist(lists:reverse(lists:keysort(#'Star'.id, StrMsg)), ?RECENT_ITEMS_COUNT))}. +% STARRED MESSAGES MANAGEMENT + +starlist(#'Roster'{favorite = Stars} = R) -> %% not used + [star(R, StarMsg, [], 0) || StarMsg <- Stars]; +starlist(RosterId) when is_integer(RosterId)-> + case kvs:get('Roster', RosterId) of {ok, R} -> starlist(R); _ -> [] end; +starlist(PhoneId) -> + starlist(roster_id(PhoneId)). + +star(#'Roster'{id = RosterId, phone = Phone} = Roster, + #'Star'{message = #'Message'{feed_id = Feed, from = MsgFrom} = Msg} = StarMsg, W, LastSync) + when is_record(W, writer); is_record(W, p2p); W == [] -> + Writer = case W of [] -> kvs_stream:load_writer(Feed); _ -> W end, + From = case Feed of + #muc{name = RoomId} -> + {Room, Unread, LastMsg} = + case kvs:get('Room', RoomId) of + {ok, FoundRoom} -> + #'Member'{id = MemId, reader = Reader} = muc_member(phone_id(Phone, RosterId), RoomId), + {UnreadCalc, LastMsgCalc, _} = unread_msg(Writer, Reader, MemId), + {FoundRoom, UnreadCalc, LastMsgCalc}; + {error, _} -> + {#'Room'{name = <<"Deleted Room">>, id = RoomId}, 0, []} + end, + Room#'Room'{unread = Unread, last_msg = LastMsg, members = [ + case muc_member(MsgFrom, RoomId) of + [] -> #'Member'{alias = <<"Deleted Account">>}; + #'Member'{reader = ReaderID} = Member -> + Member#'Member'{reader = roster:reader_cache(ReaderID)} + end]}; + _ -> +%% get From in requested roster contacts + case user(Roster, MsgFrom, Writer, LastSync) of + [] -> #'Contact'{unread = 0, last_msg = [], + names = <<"Deleted">>, surnames = <<"Account">>, status = deleted}; + Contact -> Contact + end + end, + #'ExtendedStar'{star = StarMsg#'Star'{message = Msg#'Message'{status = []}}, from = From}; +star(Local, StMsgsList, W, LastSync) -> + {ok, Roster} = kvs:get('Roster', roster_id(Local)), + star(Roster, StMsgsList, W, LastSync). + +roster(Id) when is_integer(Id) -> + R = #'Roster'{userlist = {Users, []}, roomlist = {Rooms, []}} = roster(Id, []), + R#'Roster'{userlist = Users, roomlist = Rooms}. +roster(Id, N) when is_integer(Id) -> roster:roster(Id, N, 0). +roster(#'Roster'{} = R, N, LastSync) -> + R#'Roster'{userlist = objlist(#'Roster'.userlist, R, LastSync, N), + roomlist = objlist(#'Roster'.roomlist, R, LastSync, N), + favorite = objlist(#'Roster'.favorite, R, LastSync, N)}; + +roster(Id, N, LastSync) -> case kvs:get('Roster', Id) of {ok, R} -> roster(R, N, LastSync); _ -> [] end. sub_room(subscribe, #'Member'{status = removed}) -> []; sub_room(Subscr, #'Member'{phone_id = PhoneId, feed_id = #muc{name = Room}}) -> - [n2o:Subscr(ClientId, room_topic(Room)) || #'Auth'{client_id = ClientId} <- kvs:index('Auth', user_id, PhoneId)]; + [n2o_vnode:Subscr(ClientId, room_topic(Room)) || #'Auth'{client_id = ClientId} <- kvs:index('Auth', user_id, PhoneId)]; sub_room(Subscr, Members) when is_list(Members) -> [sub_room(Subscr, M) || M <- Members]; sub_room(Subscr, <<"emqttd_", _/binary>> = ClientId) -> {ok, #'Auth'{user_id = PhoneId}} = kvs:get('Auth', ClientId), - [n2o:Subscr(ClientId, room_topic(Room)) || #'Room'{id = Room} <- get_rooms(PhoneId)]; + [n2o_vnode:Subscr(ClientId, room_topic(Room)) || #'Room'{id = Room} <- get_rooms(PhoneId)]; sub_room(Subscr, PhoneId) -> [sub_room(Subscr, muc_member(PhoneId, Room)) || #'Room'{id = Room} <- get_rooms(PhoneId)]. subscribe_room(M) -> sub_room(subscribe, M). @@ -1034,7 +1228,7 @@ sub_muc(Subscr, PhoneId, #muc{} = Feed, Clients) -> sub_muc(Subscr, PhoneId, members(Feed, active), Clients); sub_muc(Subscr, #'Roster'{id = RosterId, phone = Phone, userlist = Cntcts, roomlist = Rooms}, Members, ClientIds) -> PhoneId = phone_id(Phone, RosterId), Contacts = lists:droplast(Cntcts), - [begin n2o:Subscr(ClientId, muc_topic(PId)), n2o:Subscr(CId, muc_topic(PhoneId)) end || + [begin n2o_vnode:Subscr(ClientId, muc_topic(PId)), n2o_vnode:Subscr(CId, muc_topic(PhoneId)) end || #'Member'{phone_id = PId} <- Members, ClientId <- ClientIds, #'Auth'{client_id = CId} <- kvs:index('Auth', user_id, PId), PhoneId /= PId, @@ -1053,7 +1247,7 @@ subscribe_muc(PhoneId, Ms, Clients) -> sub_muc(subscribe, PhoneId, Ms, Clients). unsubscribe_muc(PhoneId, Ms) -> sub_muc(unsubscribe, PhoneId, Ms). unsubscribe_muc(PhoneId, Ms, Clients) -> sub_muc(unsubscribe, PhoneId, Ms, Clients). unsubscribe_muc(PhoneId) -> - [n2o:unsubscribe(ClientId, muc_topic(PhoneId)) || ClientId <- emqttd_pubsub:subscribers(muc_topic(PhoneId))]. + [n2o_vnode:unsubscribe(ClientId, muc_topic(PhoneId)) || ClientId <- emqttd_pubsub:subscribers(muc_topic(PhoneId))]. is_shared_rooms(Rooms, PhoneId2) when is_binary(PhoneId2), is_list(Rooms) -> is_shared_rooms(Rooms, get_rooms(PhoneId2)); @@ -1126,8 +1320,16 @@ is_unimembers(Members) -> length(Members) == sets:size(sets:from_list([PhoneId || #'Member'{phone_id = PhoneId} <- Members])). verify_descs(Descs) when is_list(Descs) -> - [begin #'Desc'{id = DescId} = Desc, - true = is_binary(DescId) andalso <<>> /= DescId end || Desc <- Descs], []. + [[] = validate(Desc) || Desc <- Descs], []. + +verify_thumbs(Descs) -> + lists:foldl( + fun(#'Desc'{mime = <<"thumb">>, parentid = ParentId}, {ParentIds, Ids}) -> + case Ids--[ParentId] of Ids -> {[ParentId|ParentIds], Ids}; Ids2 -> {ParentIds, Ids2} end; + (#'Desc'{mime = M, id = Id}, {ParentIds, Ids}) when M == <<"image">>; M == <<"video">> -> + case ParentIds--[Id] of ParentIds -> {ParentIds, [Id|Ids]}; ParentIds2 -> {Ids, ParentIds2} end; + (#'Desc'{}, Acc) -> Acc + end, {[], []}, Descs). status_msg(T, UID, FS) -> LStatus = [[], clear], case nentries({ok, T}, 0, UID) of @@ -1184,6 +1386,7 @@ set_seenby({ok, T}, C, S) -> NewA = erlang:setelement(Fid, A, case S of [-1] -> [-1]; [_ | _] when S0 /= [-1], S0 /= S -> lists:usort(S0 ++ S); + []-> []; _ -> lists:usort(S0) end), ok = kvs:put(NewA) end, entries(Fun, element(2, T), element(1, T), C). @@ -1220,9 +1423,11 @@ msg_filter_fun(DirSize, InnerFilterFun, UID, AccFun) -> (_, A) -> A end. msg_stop_fun(MaxReadId, UID) -> + msg_stop_fun(MaxReadId, UID, 1). +msg_stop_fun(MaxReadId, UID, Dir) -> fun(#'Message'{id = Id, status = Status} = Msg, {_Acc, #reader{pos = Pos}}) when Pos >= 0, Id =< MaxReadId -> - case Status of clear -> abs(msg_filter(Msg, UID)-1); _ -> 1 end; + case Status of clear when Dir < 0 -> abs(msg_filter(Msg, UID)-1); _ -> 1 end; (_M, _A) -> 0 end. desc_id(#'Message'{id = []} = Msg) -> @@ -1238,10 +1443,8 @@ desc_id(#'Message'{id = Id, files = Descs} = Msg) -> end, {1, []}, Descs), Msg#'Message'{id = Id, files = Descs2}; -desc_id(#'Feature'{id = [], key = Key} = F) -> F#'Feature'{id = string:lowercase(Key)}; -desc_id(#'Feature'{id = DescId, key = Key} = F) -> F#'Feature'{id = <>}. - -%%pid(M) -> case n2o_async:pid(system, M) of [] -> {ok, Pid} = M:start(), Pid; Pid -> Pid end. +desc_id(#'Feature'{id = [], key = Key} = F) -> F#'Feature'{id = list_to_binary(string:to_lower(nitro:to_list(Key)))}; +desc_id(#'Feature'{id = DescId, key = Key} = F) -> F#'Feature'{id = <>}. get_services(#'Profile'{services = Services}) -> Services; get_services(#'Member'{services = Services}) -> Services; @@ -1268,10 +1471,10 @@ set_service(#'Service'{id = [], type = Type} = S, #'Profile'{phone = Phone}=P) - set_service(S, #'Profile'{}=P) -> set_service(S, P, #'Profile'.services); set_service(S, #'Member'{}=M) -> set_service(S, M, #'Member'.services); set_service(S, #'Contact'{}=C) -> set_service(S, C, #'Contact'.services). -%%set_service(#'Service'{id = Id, type = Type = wallet}=S, T, I) -> -%% case lists:keyfind(Type, #'Service'.type, element(I, T)) of -%% false -> setelement(I, T, lists:keystore(Id, I, element(I, T), S)); -%% _ -> T end; +set_service(#'Service'{id = Id, type = wallet, status = remove}, T, I) -> + setelement(I, T, lists:keydelete(Id, #'Service'.id, element(I, T))); +set_service(#'Service'{type = wallet, status = add} = S, T, I) -> + set_service(S#'Service'{status = []}, T, I); set_service(#'Service'{id = Id}=S, T, I) -> setelement(I, T, lists:keystore(Id, #'Service'.id, element(I, T), S)). @@ -1353,14 +1556,6 @@ get_feed_data(Feed, PhoneId) -> info(?MODULE, "~p:History/get.FeedError:~p", [PhoneId, Feed]), {error, invalid_feed, [], []}. -%%obj_container(#muc{name = Room}, #'Member'{feed_id = #muc{name = Room}}) -> -%% case kvs:get('Room', Room) of -%% {ok,#'Room'{id = Room}=MUC}-> MUC; -%% _ -> {error, not_found} -%% end ; -%%obj_container(#p2p{} = Feed, #'Contact'{phone_id = PhoneId}) -> -%% get_contact(PhoneId, friend(PhoneId, Feed)). - get_feed_msg(Feed) -> get_feed_msg(Feed, 0, []). get_feed_msg(Feed, From, To) -> lists:ukeysort(#'Message'.id, [Msg || #'Message'{id = Id, feed_id = Feed2} = Msg <-kvs:all('Message'), Id >= From, Id =< To, Feed2 == Feed]). @@ -1376,7 +1571,9 @@ get_reader(Feed, PhoneId) when is_binary(PhoneId) -> get_reader(Feed, RosterId) -> case Feed of #muc{name = RoomId} -> #'Member'{reader = Reader} = muc_member(phone_id(RosterId), RoomId), Reader; - #p2p{} -> not_implemented_yet; + #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 end. @@ -1436,8 +1633,10 @@ fold(Fun, Acc, Table, Start, Finish, Driver) -> Direction = case Start > Finish of true -> #iterator.next; false -> #iterator.prev end, fold(Fun, Acc, Table, Start, Finish, Driver, Direction, ?MAX_UNREAD). -fold(Fun, Acc, Table, Start, Finish, Driver, Direction, Count) when Start /= [] -> - try case kvs:get(kvs:rname(Table), Start, Driver) of +fold(Fun, Acc, Table, Start, Finish, Driver, Direction, Count) -> + fold(Fun, Acc, Table, Start, Finish, Driver, Direction, Count, fun kvs:get/3). +fold(Fun, Acc, Table, Start, Finish, Driver, Direction, Count, GetFun) when Start /= [] -> + try case GetFun(kvs:rname(Table), Start, Driver) of {ok, R} -> Prev = element(Direction, R), Count2 = case Count of _ when is_integer(Count) -> Count - 1; @@ -1609,8 +1808,8 @@ recompile(Apps, ExcludeModules) when is_list(Apps) -> [recompile(App, ExcludeMod recompile(App, ExcludeModules) -> remove_beams(App, ExcludeModules), mad_compile:compile(App). recompile_roster(ExcludeModules) -> recompile(roster, ExcludeModules). -stop_vnodes() -> [n2o_async:stop(ring, X) || X <- lists:seq(1, length(n2o:ring()))]. -start_vnodes() -> [n2o_async:start(#handler{module = n2o_vnode, class = ring, group = n2o, state = [], name = Pos}) +stop_vnodes() -> [n2o_pi:stop(ring, X) || X <- lists:seq(1, length(n2o:ring()))]. +start_vnodes() -> [n2o_pi:start(#pi{module=n2o_vnode,table=ring,sup=n2o,state=[],name=Pos}) || {_, Pos} <- lists:zip(n2o:ring(), lists:seq(1, length(n2o:ring())))]. upd_record(Record, I, Default) -> @@ -1683,24 +1882,33 @@ sync_indexes(#table{name = Table, keys = Keys}) -> [mnesia:del_table_index(Table, Key) || Key <- IndexedKeys -- Keys], [mnesia:add_table_index(Table, Key) || Key <- Keys -- IndexedKeys]. -parse(<<"phone">>, <>) -> parse_data(Payload, ?PHONE_RE); -parse(<<"email">>, <>) -> parse_data(Payload, ?EMAIL_RE); -parse(<<"link">>, <>) -> parse_data(Payload, ?URL_RE). - -parse_desc(#'Desc'{id = Id, mime = <<"text">>, payload = Payload}) -> - lists:flatten( - [begin Objs = parse(Type, Payload), - [#'Desc'{id = <>, - mime = Type, payload = Object, parentid = Id} - || {J, Object}<- lists:zip([lists:seq(1, length(Objs))], Objs)] - end ||Type <- [<<"phone">>, <<"email">>, <<"link">>]]). -parse_desc(#'Desc'{mime = <<"text">>, payload = Payload}, RE) -> - parse_data(Payload, RE). +%%parse(Type, <>) -> +%% Types = lists:zip([<<"phone">>, <<"email">>, <<"link">>], [?PHONE_RE, ?EMAIL_RE, ?URL_RE]), +%% parse_data(Payload, proplists:get_value(Type, Types)). +parse(<<"phone">>, Payload) -> + Payload2 = re:replace(Payload, "\\d{11,}_\\d{1,}", "", [{return, binary}]), + parse_data(Payload2, ?PHONE_RE); +parse(<<"email">>, Payload) -> parse_data(Payload, ?EMAIL_RE); +parse(<<"link">>, Payload) -> parse_data(Payload, ?URL_RE). +parse_zip(Links) -> lists:zip(lists:seq(1, length(Links)), Links). +parse_desc(#'Desc'{id = DescId, mime = <<"text">>, payload = <>} = Desc) -> + [Desc|lists:flatten( + [#'Desc'{id = binary_join([DescId, Type, nitro:to_binary(I)], <<"_">>), + mime = Type, payload = Link, parentid = DescId} + || Type <- [<<"email">>, <<"link">>, <<"phone">>], + {I, Link} <- parse_zip(parse(Type, Payload))])]; +parse_desc(Desc) -> [Desc]. + parse_data(<>, RE) -> case re:run(Payload, RE, [global, {capture, first, list}]) of {match, Captured} -> [list_to_binary(C)||[C]<-Captured]; - _ -> [] - end. + _ -> [] end. + +parse_msg(#'Message'{mentioned = Mentioned}) -> + lists:flatten([case kvs:get('Member', Id) of + {ok, #'Member'{phone_id = PhoneId}} -> PhoneId; + _ -> [] + end ||Id<-Mentioned]). % FAKE NUMBERS MANAGEMENT @@ -1722,4 +1930,154 @@ delete_fake_number(PhoneNumber) when is_binary(PhoneNumber) -> case kvs:get('FakeNumbers', PhoneNumber) of {ok, _} -> kvs:delete('FakeNumbers', PhoneNumber); _ -> skip - end. \ No newline at end of file + end. + +% Whitelist MANAGEMENT + +add_whitelist_number(PhoneNumber) -> + case kvs:get('Whitelist', PhoneNumber) of + {error, _} -> kvs:put(#'Whitelist'{phone = PhoneNumber, created = roster:now_msec()}); + _ -> skip + end. + +delete_whitelist_number(PhoneNumber) -> + case kvs:get('Whitelist', PhoneNumber) of + {ok, _} -> kvs:delete('Whitelist', PhoneNumber); + _ -> skip + end. + +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. + +%% tables storage +save_db(Path) -> + Data = lists:append([kvs:all(B) || B <- [Name || {table,Name} <- kvs:dir()-- + [{table,schema},{table,mqtt_session},{table,mqtt_admin}]]]), + kvs:save(Path, Data). + +load_db(Path) -> + kvs:add_seq_ids(), + AllEntries= try kvs:load(Path) catch _:_ -> [] end, + [begin case element(1,E) of + mqtt_trie=Tab -> write_trie(Tab,setelement(1,E,trie)); + mqtt_trie_node=Tab -> write_trie(Tab,setelement(1,E,trie_node)); + _ -> kvs:put(E) end end || E <- lists:filter(fun(E) -> is_tuple(E) end ,AllEntries)]. + +write_trie(Tab,Trie) ->mnesia:dirty_write(Tab, Trie). + + +n2o_pid(Name) -> + case n2o_async:pid(system, Name) of + Pid when is_pid(Pid) -> + case process_info(Pid) of + undefined -> +%% [supervisor:F(roster, {system, Name}) || F <- [terminate_child, delete_child]], +%% n2o:cache(system, {system, Name}, undefined), + {Pid,_}=wait_for(fun ()-> restart_module(system, Name) end), Pid; + _ -> Pid end; + _ -> wait_for(fun ()-> restart_module(system, Name) end) end. + + +client_pid(Pid,M) -> + case process_info(Pid) of + undefined -> restart_module(M); +% {ok,NewPid}=wait_for(fun()-> emqttc:start_link([{client_id, ClientId}, {logger, {console, error}}, {reconnect, 5}]) end), NewPid; + _ -> Pid end. + +restart_module(M) -> + wait_for(fun ()-> restart_module(system, M) end). + +restart_module(Pid,M) when is_pid(Pid)-> + case process_info(Pid) of + undefined -> + n2o_async:pid(system, roster_profile) ! {restart, M}; + _ -> Pid end; + +restart_module(Class,Name) -> + H=case n2o_async:pid(Class,Name) of + Pid when is_pid(Pid) -> + #handler{group=Group} = Async=case process_info(Pid) of + undefined -> #handler{module = Name, class = Class, group = roster, name = Name, state = []}; + _ -> n2o_async:send(Pid,{get}) + end, + [ supervisor:F(Group,{Class,Name}) || F <- [ terminate_child , delete_child ] ], + n2o:cache(Class,{Class,Name},undefined), + Async; + _ -> #handler{module = Name, class = Class, group = roster, name = Name, state = []} + end, + {NPid,_}=n2o_async:start(H), NPid. + + +wait_for(Fun) -> + case Fun() of + {error,_} -> timer:sleep(1000), wait_for(Fun); + R -> R end. + + +link_thumb() -> + backup(), + roster_db:upd_msgs(fun roster:link_thumb/1), + ok. +%% NOTE! Need empty state mapping because of roster_db:upd_msgs/1 fun +link_thumb([]) -> []; +link_thumb(#'Message'{files = Payload} = Msg) -> + ThumbMime = <<"thumb">>, + VerifiedPayload = link_thumb(<<"video">>, ThumbMime, link_thumb(<<"image">>, ThumbMime, Payload)), + Msg#'Message'{files = VerifiedPayload}. +link_thumb(ParentType, ChildType, Payload) -> +%% find parent: image or video + case lists:keyfind(ParentType, #'Desc'.mime, Payload) of + false -> Payload; + #'Desc'{id = ParentId, payload = ParentPayload} -> +%% find child - thumb + ThumbDesc = case lists:keyfind(ChildType, #'Desc'.mime, Payload) of + false -> +%% create thumb because it is not exists in the message + ThumbPayload = case ParentType of <<"image">> -> ParentPayload; <<"video">> -> ?VIDEO_THUMBNAIL_DEFAULT end, + #'Desc'{id = roster_data:gen_desc_id(), mime = ChildType, parentid = ParentId, payload = ThumbPayload}; + FoundDesc -> FoundDesc#'Desc'{parentid = ParentId} + end, + lists:keystore(ChildType, #'Desc'.mime, Payload, ThumbDesc) + end. + +generate_server_id() -> iolist_to_binary(["srv_", nitro:to_binary(roster:now_msec())]). + +%% TODO add test for the func below +%% Set Message.next to [] if it is message about history deletion +%% This messages payload is <<"History was removed">> or/and message Msg.id starts with <<"rmv_history_">> +check_message(MsgId) when is_integer(MsgId) -> + {ok, Msg} = kvs:get('Message', MsgId), + check_message(Msg); +check_message(#'Message'{msg_id = <<"rmv_history_",_/binary>>} = Msg) -> + Msg#'Message'{next = []}; +check_message(#'Message'{files = Descs} = Msg) -> + case lists:keyfind(<<"History was removed">>, #'Desc'.payload, Descs) of + false -> Msg; + _ -> Msg#'Message'{next = []} + end. + +delete_call_bubbles() -> + backup(), + roster_db:upd_msgs(fun roster:delete_call_bubbles/1), + ok. +delete_call_bubbles([]) -> []; +delete_call_bubbles(#'Message'{files = [#'Desc'{mime = MsgMime}], seenby = MsgSeenBy} = Msg) when MsgMime == ?CONTENT_TYPE_VIDEOCALL; MsgMime == ?CONTENT_TYPE_AUDIOCALL -> + delete_call_bubbles(Msg, MsgSeenBy); +delete_call_bubbles(Msg) -> Msg. +delete_call_bubbles(Msg, [-1]) -> Msg; +delete_call_bubbles(Msg, _) -> + io:format("FoundCallBubbleMsg:~p~n", [Msg]), + Msg#'Message'{seenby = [-1]}. + +cashe_ses(PhoneId, Term) -> + [begin + Now = integer_to_binary(now_msec()), + K = <<"cashe_", Now/binary>>, + F = #'Feature'{id = <<>>, key = K, value = Term, group = <<"CACHE_GROUP">>}, + kvs:put(Auth#'Auth'{settings = lists:keystore(K, #'Feature'.key, Settings, F)}) + end||#'Auth'{client_id = ClientId, settings = Settings} = Auth <-kvs:index('Auth', user_id, PhoneId), + emqttd_cm:lookup_proc(ClientId) == undefined]. +send_cache(#'Auth'{client_id = ClientId, settings = Settings} = Auth, C) -> + [send_action(C, ClientId, V) || #'Feature'{key = <<"cache_", _/binary>>, value = V} <- Settings], + kvs:put(Auth#'Auth'{settings = [F||#'Feature'{group = Group} = F<-Settings, Group /= <<"CACHE_GROUP">>]}). \ No newline at end of file diff --git a/apps/roster/src/roster_client.erl b/apps/roster/src/roster_client.erl index 217140c332f7f774a424cac0ecc9b1e02b7ee68d..622560118db36dcb9eee752323ed53adae1bad3c 100644 --- a/apps/roster/src/roster_client.erl +++ b/apps/roster/src/roster_client.erl @@ -3,44 +3,46 @@ -include_lib("n2o/include/n2o.hrl"). -include_lib("emqttc/include/emqttc_packet.hrl"). -include("roster.hrl"). +-include("micro.hrl"). -include("roster_test.hrl"). --record(mqttc, {client :: pid(), - status :: connected}). +-record(mqttc, {client :: pid(), status :: connected}). +-define(CVERSION,<<"version/10">>). %% client version -define(DEFAULT_MQTTC_OPTS, [{clean_sess, true}, - {will, [{qos, 0}, {retain, false}, {topic, ?VERSION}, {payload, <<"I die">>}]}]). - --record(state, {mqttc = [], mqtt_opts = [], client_id = <<>>, username = <<"api">>, - receive_pid = [], filter = filter, last_res = []}). + {will, [{qos, 0}, {retain, false}, {topic, ?CVERSION}, {payload, <<"I die">>}]}]). set_config(Key, Val) -> application:set_env(?MODULE, Key, Val). get_config(Key, Default) -> application:get_env(?MODULE, Key, Default). -start_client(Name) -> start_client(Name, <<>>, []). -start_client(Name, Token) -> start_client(Name, Token, []). -start_client(Name, Token, Opts) -> +start_client(ClientId) -> start_client(ClientId, <<>>, [{host, ?HOST}]). +start_client(ClientId, Token) -> start_client(ClientId, Token, [{host, ?HOST}]). +start_client(ClientId, Token, Opts) -> receive_drop(), - stop_client(Name), + stop_client(ClientId), + Username = proplists:get_value(username, Opts, <<"api">>), n2o_async:start(#handler{module = ?MODULE, class = system, group = roster, - state = #state{mqtt_opts = [{password, Token}|Opts ++ ?DEFAULT_MQTTC_OPTS], - receive_pid = self()}, name = Name}), - init = receive_test(Name, init), ok. - -start_cli_receive(Name, Token) -> start_cli_receive(Name, Token, 0). -start_cli_receive(Name, Token, Counter) -> start_cli_receive(Name, Token, Counter, [{host, ?HOST}]). -start_cli_receive(Name, Token, Counter, Opts) -> start_client(Name, Token, Opts), - receive_test(Name, #'Auth'{}, Counter). - -stop_client(Names) when is_list(Names) -> [stop_client(Name) || Name <- Names], ok; -stop_client(Name) -> - case n2o_async:pid(system, Name) of + state = #state{mqtt_opts = [{password, Token}|Opts ++ ?DEFAULT_MQTTC_OPTS], username = Username, + receive_pid = self()}, name = ClientId}), + init = receive_test(ClientId, init), ok. + +start_cli_receive(ClientId, Token) -> start_cli_receive(ClientId, Token, 0). +start_cli_receive(ClientId, Token, Counter) -> start_cli_receive(ClientId, Token, Counter, [{host, ?HOST}]). +start_cli_receive(ClientId, Token, Counter, Opts) -> start_client(ClientId, Token, Opts), + case ?CVERSION of + <<"version/10">> -> receive_test(ClientId, #'Auth'{}, Counter); + _ -> send_receive(ClientId, Counter+1, #'Profile'{status = get}) + end. + +stop_client(ClientIds) when is_list(ClientIds) -> [stop_client(ClientId) || ClientId <- ClientIds], ok; +stop_client(ClientId) -> + case n2o_async:pid(system, ClientId) of Pid when is_pid(Pid) -> case process_info(Pid) of undefined -> - [supervisor:F(roster, {system, Name}) || F <- [terminate_child, delete_child]], - n2o:cache(system, {system, Name}, undefined), ok; - _ -> n2o_async:stop(system, Name), ok end; _ -> ok end. + catch [supervisor:F(roster, {system, ClientId}) || F <- [terminate_child, delete_child]], + n2o:cache(system, {system, ClientId}, undefined), ok; + _ -> n2o_async:stop(system, ClientId), ok end; _ -> ok end. receive_drop() -> receive _ -> receive_drop() after ?DRP_TMOUT -> skip end. @@ -55,7 +57,7 @@ filter(Term, _) -> #'Member'{presence = Presence, status = Status} when Status /= patch andalso (Presence == online orelse Presence == offline) -> skip; %% ignore Member presence - _ ->%n2o:info("Term value: ~p", [Term]), + _ -> % roster:info("Term value: ~p", [Term]), send end. filter_friend(Term, State) -> case {Term, filter(Term, State)} of @@ -64,9 +66,19 @@ filter_friend(Term, State) -> {_, Res} -> Res end. proc(#mqttc{}, #handler{} = H) -> {reply, [], H}; +proc(init, #handler{name = ?SYS_REST_CLIENT, state = #state{receive_pid = Self} = State} = Async) -> + {ok, C} = application:get_env(rest, rest_pid), + Self ! init, + {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", []), + 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]), - {ok, C} = emqttc:start_link([{host, ?HOST}, {client_id, ClientId}, + {ok, C} = emqttc:start_link([{client_id, ClientId}, {clean_sess, false}, {logger, {console, error}}, {username, Username}, {reconnect, 5}] ++ Opts), Self ! init, @@ -79,6 +91,9 @@ proc({filter, Filter}, #handler{state = #state{receive_pid = Self} = State} = H) proc(last_res, #handler{state = #state{receive_pid = Self, last_res = LastRes} = State} = H) -> Self ! lists:ukeysort(1, LastRes), {reply, [], H#handler{state = State#state{last_res = []}}}; +proc(unsort_last_res, #handler{state = #state{receive_pid = Self, last_res = LastRes} = State} = H) -> + Self ! LastRes, + {reply, [], H#handler{state = State#state{last_res = []}}}; proc({publish, _, BinTerm}, #handler{state = #state{receive_pid = Self, last_res = LastRes, filter = FilterFun} = State} = H) -> case ?MODULE:FilterFun(Term = binary_to_term(BinTerm), State) of send -> @@ -88,6 +103,9 @@ proc({publish, _, BinTerm}, #handler{state = #state{receive_pid = Self, last_res proc(_Term, #handler{state = #state{mqttc = {error, _} = Err}} = H) -> roster:info("mqttc error: ~p", [Err]), {reply, [], H}; +proc({callback, CallbackFun}, #handler{state = State} = H) -> + State2 = CallbackFun(State), + {reply, [], H#handler{state = State2}}; proc(Term, #handler{state = #state{mqttc = C}} = H) -> roster:send_event(C, <<>>, <<>>, Term), {reply, [], H}. @@ -108,15 +126,19 @@ gen_anylist(N, Prefix) -> [iolist_to_binary([Prefix, integer_to_binary(D)]) || D rosters(_ClientId, Phone) -> {ok, #'Profile'{rosters = RosterIds}} = kvs:get('Profile', Phone), [roster:phone_id(Phone, Id) || Id <- RosterIds]. +rosters(#'Profile'{rosters = Rosters, phone = Phone}) -> + [roster:phone_id(Phone, Id) || #'Roster'{id = Id} <- Rosters]. reg_fake_user(Phone) -> reg_fake_user(Phone, <<"DevKey_", Phone/binary>>, 1000, []). reg_fake_user(Phone, DevKey) -> reg_fake_user(Phone, DevKey, 1000). reg_fake_user(Phone, DevKey, Sleep) -> reg_fake_user(Phone, DevKey, Sleep, []). reg_fake_user(Phone, DevKey, Sleep, Opts) -> reg_fake_user(Phone, DevKey, Sleep, Opts, - [#'Feature'{key = ?DEFAULT_LANGUAGE_KEY, value = <<"English:en">>, group = ?LANG_KEY}]). + [#'Feature'{id = <>, key = ?DEFAULT_LANGUAGE_KEY, value = <<"English:en">>, group = ?LANG_KEY}]). reg_fake_user(Phone, DevKey, Sleep, Opts, Features) -> RegClientId = gen_name_reg(Phone, DevKey), + receive_drop(), + stop_client(RegClientId), start_client(RegClientId, <<>>, Opts), #io{code = #ok2{src = {ClientId, Token}}} = send_receive(RegClientId, #'Auth'{type = reg, dev_key = DevKey, sms_code = <<"12">>, phone = {fake, Phone}, settings = Features}), @@ -127,8 +149,11 @@ client_ids(Phone) -> [ClientId || #'Auth'{client_id = ClientId} <- kvs:index('Au client_id(Phone) -> hd(client_ids(Phone)). %% Direct call Module:info(...) +test_info(Module, Term, #cx{} = State) -> + Module:info(Term, [], State); test_info(Module, Term, Phone) -> - Module:info(Term, [], #cx{params = client_id(Phone)}). + test_info(Module, Term, #cx{params = client_id(Phone)}). + test_info(#'History'{roster_id = Phone, feed = Feed, data = Data} = History) -> test_info(roster_history, @@ -158,7 +183,10 @@ receive_test(ClientId, Term, Counter, Timeout) -> receive_test(ClientId, Term, Counter - 1); {#'Message'{status = []}, #'Message'{status = []}} -> R; {#'Job'{}, _} -> R; + {#'Link'{}, _} -> R; + {#'Message'{},#'Ack'{}} -> R; {#'Message'{status = []}, #io{code = #error{}} = IO} -> IO; + {#'Message'{status = []}, #errors{} = IO} -> IO; {#'Message'{status = []}, _} -> receive_test(ClientId, Term, Counter); {_, _} when Counter > 0 -> receive_test(ClientId, Term, Counter - 1); _ -> R end diff --git a/apps/roster/src/roster_data.erl b/apps/roster/src/roster_data.erl index c7cfbf25441a12c34840422fcb7628dd5db169aa..a03632729ca5b907b314c53dee53755ebf0ee655 100644 --- a/apps/roster/src/roster_data.erl +++ b/apps/roster/src/roster_data.erl @@ -13,11 +13,14 @@ add_test_objects() -> friend_test_rand(20000), %% P2P chats on the server - 20K+ create_rand_room(10000), %% Rooms/group on the server total - 10K+ friend_test_hard_rand(10000), %% Contacts per user - 2K + ( 5 users per server ) + [create_hard_member_room(2) || _<-lists:seq(1, 10000)], %% Create 2K+ rooms for a user ( 5 users per server ) HardRooms = [create_hard_member_room(500) || _<-lists:seq(1, 5)], %% Rooms/Groups for a user - 500 + ( 5 users per server ) - [add_huge_count_msgs(start, #muc{name = Room}, 10000) || Room<-HardRooms], %% Rooms/Groups with 10K+ messages history ( 5 rooms per server ) + + [add_huge_count_msgs(start, #muc{name = Room}, 100) || Room<-HardRooms, _ <- lists:seq(1, 100)], %% Rooms/Groups with 10K+ messages history ( 5 rooms per server ) + [add_star_msgs(Phone, 1000) || Phone <- hard_phones()], [begin #'Contact'{phone_id = FriendId} = hd(roster:get_contacts(roster:roster_id(Phone))), - add_huge_count_msgs(start, roster:feed_key(p2p, roster:phone_id(Phone), FriendId), 10000) - end || Phone<-hard_phones()], %% P2P chat with 10K+ message history ( 5 chat per server ) + add_huge_count_msgs(start, roster:feed_key(p2p, roster:phone_id(Phone), FriendId), 100) + end || Phone<-hard_phones(), _ <-lists:seq(1, 100)], %% P2P chat with 10K+ message history ( 5 chat per server ) [create_channel() || _<-lists:seq(1, 10000)]. %% Channels on the server total - 10K+ add_test_users(N, Procs) -> @@ -36,6 +39,17 @@ hard_rosters() -> hard_roster_ids() -> [roster:roster_id(Phone) || Phone <- hard_phones()]. +reg_fake_user(Phone) -> reg_fake_user(Phone, <<"DevKey">>). +reg_fake_user(Phone, DevKey) -> + RegClientId = roster_client:gen_name_reg(Phone, DK = <<"DevKey", "_", Phone/binary>>), + Settings = [#'Feature'{id = <>, key = ?DEFAULT_LANGUAGE_KEY, value = <<"English:en">>, group = ?LANG_KEY}], + {reply, {bert, #io{code = #ok2{src = {ClientId, Token}}}}, _,_} + = roster_client:test_info(roster_auth, + #'Auth'{type = reg, dev_key = DK, sms_code = <<"12">>, phone = {fake, Phone}, settings = Settings}, + #cx{params = RegClientId}), + {ok, #'Auth'{token = Token}} = kvs:get('Auth', ClientId), + {ClientId, roster:phone_id(Phone), Token}. + add_users(N) when is_integer(N)-> add_users([list_to_binary(io_lib:format("~11..0s",[integer_to_list(rand:uniform(999999999999), 10)])) || _<- lists:seq(1, N)]); @@ -66,7 +80,7 @@ rand_roster_fun(RosterIds) -> rand_roster() -> rand_roster(lists:ukeysort(#'Roster'.id, [R || #'Roster'{} = R<-kvs:all('Roster')])). rand_roster(Rosters) -> - N = rand:uniform(length(Rosters)+1), lists:nth(N, Rosters). + N = rand:uniform(length(Rosters)), lists:nth(N, Rosters). start_cli_processes(Procs, Fun, Args) when is_atom(Fun)-> [spawn(?MODULE, Fun, Args) ||_<-lists:seq(1, Procs)]. @@ -88,7 +102,7 @@ friend_test_hard_rand_fun(N) -> fun() -> [FriendHardFun()||_<- lists:seq(1, N)] end. friend_test_hard_rand() -> - friend_test_rand([rand_roster(hard_rosters()), rand_roster(kvs:all('Roster'))]). + friend_test_rand([rand_roster(hard_rosters()), rand_roster(get_roster_ids())]). friend_test_hard_rand(N) -> [friend_test_hard_rand()||_<- lists:seq(1, N)]. @@ -101,29 +115,33 @@ friend_test_rand_fun(N) -> start_rosters(RosterIds) -> Rosters = [case R of #'Roster'{} -> R; _-> element(2, kvs:get('Roster', R)) end||R<-RosterIds], - [begin + lists:flatten([try PhoneId = roster:phone_id(Phone, Id), #'Auth'{client_id = ClientId, token = Token} = hd([Auth || Auth <- kvs:index('Auth', user_id, PhoneId)]), start_cli_receive_infinity(ClientId, Token), - {PhoneId , ClientId, Token} - end || #'Roster'{id = Id, phone = Phone} <-Rosters]. + {PhoneId, ClientId, Token} + catch + _:_ ->[] + end || #'Roster'{id = Id, phone = Phone} <-Rosters]). friend_test_rand() -> friend_test_rand([rand_roster() || _<- lists:seq(1, 2)]). friend_test_rand(N) when is_integer(N) -> - [friend_test_rand()||_<- lists:seq(1, N)]; + [friend_test_rand() ||_<- lists:seq(1, N)]; friend_test_rand(RosterIds) -> try Rosters = [case R of #'Roster'{} -> R; _-> element(2, kvs:get('Roster', R)) end||R<-RosterIds], Counter = length(Rosters), - - Data = [{APhoneId, AClientId, AToken}, {BPhoneId, BClientId, BToken}] = start_rosters(Rosters), - {_PhoneIds, ClientIds, _TokenIds} = lists:unzip3(Data), - roster_client:set_filer([AClientId, BClientId], filter_friend), - roster_client:send_receive(AClientId, Counter, #'Friend'{phone_id = APhoneId, friend_id = BPhoneId, status = request}), - roster_client:send_receive(BClientId, Counter + 1, #'Friend'{phone_id = BPhoneId, friend_id = APhoneId, status = confirm}), - roster_client:set_filer([AClientId, BClientId], filter), - lists:map(fun roster_client:stop_client/1, ClientIds), ok +%% 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)), + roster_client:test_info(roster_friend, #'Friend'{phone_id = BPhoneId, friend_id = APhoneId, status = confirm}, roster:phone(BPhoneId)) +%% {_PhoneIds, ClientIds, _TokenIds} = lists:unzip3(Data), +%% roster_client:set_filer([AClientId, BClientId], filter_friend), +%% roster_client:send_receive(AClientId, Counter, #'Friend'{phone_id = APhoneId, friend_id = BPhoneId, status = request}, 30000), +%% roster_client:send_receive(BClientId, Counter-1, #'Friend'{phone_id = BPhoneId, friend_id = APhoneId, status = confirm}, 30000), +%% 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 end. @@ -149,9 +167,11 @@ create_room(RosterIds, Room, PageMembCount) -> create_room(RosterIds, Room, PageMembCount, group). create_room(RosterIds, Room, PageMembCount, Type) -> try - [AdmRoster|Rosters] = [case R of #'Roster'{} -> R; _-> element(2, kvs:get('Roster', R)) end || R<-RosterIds], - Counter = length(Rosters), - Data = [{AdmPhoneId, AClientId, _}] = start_rosters([AdmRoster]), + [#'Roster'{phone = AdmPhone, id = ARosterId}|Rosters] = [case R of #'Roster'{} -> R; _-> element(2, kvs:get('Roster', R)) end || R<-RosterIds], +%% Counter = length(Rosters), + AdmPhoneId = roster:phone_id(AdmPhone, ARosterId), + +%% Data = [{AdmPhoneId, AClientId, _}] = start_rosters([AdmRoster]), Members = [#'Member'{status = member, phone_id = roster:phone_id(Phone, Id), presence = online, alias = gen_alias()} || #'Roster'{id = Id, phone = Phone}<-Rosters], Mems = [HdMembers|TlMembers] = split_page(PageMembCount, Members), @@ -159,12 +179,16 @@ create_room(RosterIds, Room, PageMembCount, Type) -> group -> {[#'Member'{status = admin, phone_id = AdmPhoneId, alias = gen_alias(), presence = online}], [], HdMembers}; channel -> {[], [#'Link'{name = gen_alias()}], []} end, - roster_client:send_receive(AClientId, 1, #'Room'{status = create, type = Type, - id = Room, name = Room, admins = Admins, members = Members2, links = Links}), - Mmbrs2 = case Type of group -> TlMembers; channel -> Mems end, - [roster_client:send_receive(AClientId, 1, #'Room'{status = add, id = Room, members = Mmbrs})|| Mmbrs <-Mmbrs2, Mmbrs /= []], - roster_client:stop_client(AClientId) + roster_client:test_info(roster_room, #'Room'{status = create, type = Type, + id = Room, name = Room, admins = Admins, members = Members2, links = Links}, roster:phone(AdmPhoneId)), + +%% roster_client:send_receive(AClientId, 1, #'Room'{status = create, type = Type, +%% id = Room, name = Room, admins = Admins, members = Members2, links = Links}), + Mmbrs2 = case Type of group -> TlMembers; channel -> Mems end, +%% [roster_client:send_receive(AClientId, 1, #'Room'{status = add, id = Room, members = Mmbrs})|| Mmbrs <-Mmbrs2, Mmbrs /= []], + [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)]) end, Room. @@ -305,21 +329,3 @@ send_to_vnode(ClientId, Term) -> get_star_client_id(PhoneId, MsgId) -> iolist_to_binary([PhoneId, <<"_">>, integer_to_binary(MsgId)]). - -%% get all star msgs and fill Star.client_id if it is missed -fix_star_msgs(Phone) when is_binary(Phone) -> - fix_star_msgs(roster:roster_id(roster:get_phone_id_by_phone(Phone))); -fix_star_msgs(RosterId) -> - case kvs:get('Roster', RosterId) of - {ok, #'Roster'{favorite = StoredStars} = StoredRoster} -> - PhoneId = roster:get_phone_id_by_roster(RosterId), - UpdatedStarred = [case StoredClientId of - [] -> StoredStar#'Star'{client_id = get_star_client_id(PhoneId, MsgId)}; - _ -> StoredStar - end || #'Star'{client_id = StoredClientId, message = #'Message'{id = MsgId}} = StoredStar <- StoredStars], - kvs:put(StoredRoster#'Roster'{favorite = UpdatedStarred}), - roster:info(?MODULE, "UpdateFavoritesForRoster:~p", [RosterId]); - ErrorRes -> ErrorRes - end. - -%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \ No newline at end of file diff --git a/apps/roster/src/roster_db.erl b/apps/roster/src/roster_db.erl index 5e9a58814cb0073655c436022899d539a4635d2f..d8febe3ea2289fe256bbb68d06dec2a859d1ade4 100644 --- a/apps/roster/src/roster_db.erl +++ b/apps/roster/src/roster_db.erl @@ -4,6 +4,7 @@ -include("roster.hrl"). -include_lib("emqttd/include/emqttd.hrl"). -include_lib("n2o/include/n2o.hrl"). +-include_lib("kvs/include/metainfo.hrl"). fix_msg_container() -> RosterRes = lists:flatten([case R#'Roster'{favorite = [Msg#'Message'{container = chain} ||#'ExtendedStar'{star = Msg}<-Stars]} of @@ -288,7 +289,7 @@ online_user_country(#'Roster'{phone = Phone} =Roster, Acc) -> _ -> Acc end. user_country(#'Roster'{phone = Phone}, Acc) -> - case phone_utils:mobile_phone_number_info(<<"+", Phone/binary>>) of + case libphonenumbers:mobile_phone_number_info(<<"+", Phone/binary>>) of #{country_metadata := #{id := Id}, valid := true} -> roster:inc_key(Id, Acc); _ -> Acc end. @@ -358,84 +359,176 @@ search_voice_reply() -> end end || #'Message'{type = [reply], link = Id} <- kvs:all('Message')]). +opt_put(OldRec, OldRec) -> []; +opt_put(NewRec, _OldRec) -> kvs:put(NewRec). + upd_msgs(Fun) when is_function(Fun) -> - [kvs:put(Fun(M))||M<-kvs:all('Message')], - [kvs:put(W#writer{first = Fun(F), cache = Fun(C)})||#writer{cache = C, first = F} = W<-kvs:all(writer), - (is_record(C, 'Message') orelse C==[]) andalso (F==[] orelse is_record(F, 'Message'))], - [kvs:put(R#'Roster'{favorite = [S#'ExtendedStar'{star = Fun(M)}||S=#'ExtendedStar'{star = #'Message'{} = M}<- Stars]}) - || R = #'Roster'{favorite = Stars} <- kvs:all('Roster')], ok; + [opt_put(Fun(M), M) || M <- kvs:all('Message')], + [opt_put(W#writer{first = Fun(F), cache = Fun(C)}, W) || #writer{cache = C, first = F} = W <- kvs:all(writer), + (is_record(C, 'Message') orelse C == []) andalso (F == [] orelse is_record(F, 'Message'))], + [opt_put(R#'Roster'{favorite = [S#'Star'{message = Fun(Msg)} || #'Star'{message = Msg} = S <- Stars]}, R) + || R = #'Roster'{favorite = Stars} <- kvs:all('Roster')], + [opt_put(J#'Job'{data = [Fun(M) || M <- Msgs]}, J) || J = #'Job'{data = Msgs} <- kvs:all('Job')], + ok; upd_msgs(Fun) when is_atom(Fun)-> upd_msgs(fun ?MODULE:Fun/1). +upd(#'Message'{files = Descs} = Msg) -> + Msg#'Message'{files = lists:flatten([roster:parse_desc(Desc)|| #'Desc'{} = Desc<-Descs])}. + +migrate_link_re() -> + lists:flatten([case upd(M) of + #'Message'{files = Descs2} when length(Descs) == length(Descs2) -> []; + Msg -> Msg + end || M = #'Message'{files = Descs, type = Type}<-kvs:all('Message'), Type /= [sys]]). + fix_migration_table() -> kvs:put({schema_migrations, 20180710150211, true}). fix_invalid_rooms() -> [roster:purge_room(Room)||#writer{count = 0, first = [], id = {muc, Room}}<-kvs:all(writer)]. - + get_ips(Begin) -> sets:to_list(sets:from_list([A||A = #'Auth'{settings = Settings}<-kvs:all('Auth'), #'Feature'{key = <<"IP">>, value = IP} <-Settings, Begin == hd(binary:split(IP, <<".">>))])). + +validate() -> + lists:flatten([validate_table(Table) || #table{name = Table}<-roster:tables(), Rec<-kvs:all(Table), + not lists:member(Table, [chain, writer, reader])]). + +validate_table(Table) -> + lists:flatten([case roster_validator:validate(Rec) of {error, Acc} -> throw({error, Acc}); ok -> [] end + || Rec<-kvs:all(Table)]). + +%%validate2() -> +%% lists:flatten([validate_table2(Table) || #table{name = Table}<-roster:tables(), Rec<-kvs:all(Table), +%% not lists:member(Table, [chain, writer, reader])]). + +validate_table2(Table) -> + lists:flatten([case roster_validator:validate(Rec) of ok -> [];Err -> Err end + || Rec<-kvs:all(Table)]). + +upd_services_in_contacts() -> %% status add to added + lists:flatten([case R#'Roster'{userlist = [C#'Contact'{services = + [Serv#'Service'{status = case St of add-> added; _-> St end} ||#'Service'{status = St} = Serv<-Ss]} + ||C=#'Contact'{services = Ss}<-Contacts]} of + R -> []; + R2 -> kvs:put(R2) + end ||#'Roster'{userlist = Contacts}=R<-kvs:all('Roster')]). + +fix_msg(#'Message'{type = Type} = Msg) when is_atom(Type) -> + fix_msg(Msg#'Message'{type = [Type]}); +fix_msg(#'Message'{status = St} = Msg) when St==internal;St==client -> + fix_msg(Msg#'Message'{status = []}); +fix_msg(#'Message'{link = <>} = Msg) -> + fix_msg(Msg#'Message'{link = binary_to_integer(Link)}); +fix_msg(#'Message'{files = Descs} = Msg) -> + Msg#'Message'{files = [D#'Desc'{mime = case M of [] -> <<"text">>; _ -> M end} + || #'Desc'{mime = M} = D <-Descs]}; +fix_msg(Msg) -> Msg. +fix_msgs() -> upd_msgs(fix_msg). + +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)) + 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() +. -valid_chains({Tab, feed}, Id, Acc) -> - [Entity] = mnesia:dirty_read(Tab, Id), +%%fix_chain(#writer{count =N, cache = EC, first = EF}=W) when element(2,EC)>element(2,EF), N>1 -> +%% case kvs:get(element(1,EC),element(2,EC)) of +%% {ok,E}-> set_prev(EF,E,N); +%% _ -> W +%% end; +%% +%%fix_chain(#writer{count =N, cache = EC, first = EF}=W) -> []. + + +set_prev(E,Start,N)-> + Fun = fun(A, Acc) -> [A | Acc] end, + % roster:info(?MODULE, "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|_] -> + SId=element(2,H1), Stop=element(2,E), + case element(#iterator.next,H2) of + Stop ->NH1=erlang:setelement(#iterator.prev,H1,element(2,H2)), kvs:put(NH1); + SId ->NH1=erlang:setelement(#iterator.prev,H1,element(2,H2)), kvs:put(NH1), set_prev(E,NH1,N); + _ -> H1 + end; + Er -> Er + end. + +valid_chains({Tab,feed},Id,Acc) -> + [Entity]=mnesia:dirty_read(Tab,Id), % roster:info(?MODULE, "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}); + Feed=element(#iterator.feed_id, Entity), + case lists:keyfind(Feed, 1, Acc) of + {Feed,Ids} -> lists:keyreplace(Feed, 1, Acc, {Feed,[Id]++Ids}); _ -> case kvs_stream:load_writer(Feed) of #writer{} -> [{Feed, [Id]}] ++ Acc; _ -> kvs:remove(element(1, Entity), element(2, Entity)), Acc end end; -valid_chains({Tab, Feed}, Id, Acc) -> - [Entity] = mnesia:dirty_read(Tab, Id), +valid_chains({Tab,Feed},Id,Acc) -> + [Entity]=mnesia:dirty_read(Tab,Id), % roster:info(?MODULE, "Start:~p", [Acc]), case element(#iterator.feed_id, Entity) of - Feed -> [Id] ++ Acc; - _ -> Acc + Feed ->[Id]++Acc; + _ -> Acc end. -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(_,[],_) -> []; +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) -> %NewL=[H|T]=lists:usort(List), % roster:info(?MODULE, "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)) + 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, - {AOut ++ [A], TIn} - end, - lists:foldl(Fun, {[], T}, NewL), ok -. + 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) -> @@ -447,19 +540,82 @@ dirty_foldl(Fun, Acc, Tab, E, Key) -> NxtKey = mnesia:dirty_next(Tab, Key), dirty_foldl(Fun, NxtAcc, Tab, E, NxtKey). + %% @doc Repair chains -spec fix_chains(atom()) -> list(). fix_chains(Tab) -> - [begin #writer{count = C} = kvs_stream:load_writer(Feed), - N = length(Acc), C1 = case N - C < 0 of true -> N; _ -> C end, - {_, L2} = lists:split(N - C1, lists:usort(Acc)), repair_chain(Tab, L2, C1) end - || {Feed, Acc} <- dirty_foldl(valid_chains, [], {Tab, feed}), Acc /= []]. + [ begin #writer{count=C}=kvs_stream:load_writer(Feed), + N=length(Acc), C1=case N-C<0 of true -> N; _ -> C end , + {_,L2}=lists:split(N-C1,lists:usort(Acc)), repair_chain(Tab,L2,C1) end + || {Feed,Acc}<-dirty_foldl(valid_chains, [], {Tab,feed}), Acc/=[]]. + +get_chain(Tab,Feed) -> + [ case kvs:get(Tab,Id) of {ok, E}-> E; _-> [] end || + Id<-lists:usort(dirty_foldl(valid_chains, [], {Tab,Feed}))]. + +%%fix_chains(Tab) -> +%% [ repair_chain(Tab,dirty_foldl(valid_chain, [], Tab, Feed)) || #writer{id=Feed}<-kvs:all('writer') ]. + + +%% Mnesia cluster backup +%% +backup()-> ExcTabs= [mqtt_session,mqtt_admin], + backup(mnesia:system_info(tables)-ExcTabs). + +backup(Tabs) -> + Date = roster:local_time_as_gmt(calendar:local_time()), + Node = node(), +% {ok, ServerAddress} = vox_api:get_server_ip(), + File = io_lib:format("./~s_bkp_~s", [Node, Date]), + backup(false, Tabs, Node, File, []). + +backup(Schema, Tables, Node, File, Opts) when is_list(Tables) -> + Tabs = Tables -- if Schema -> []; true -> [schema] end, + Opts2 = lists:keystore(max, 1, Opts, {max, Tabs}), + case mnesia:activate_checkpoint(Opts2) of + {ok, Name, _Nodes} -> + case mnesia:backup_checkpoint(Name, File) of + ok -> + mnesia:deactivate_checkpoint(Name); + {error, Reason} = Err -> + roster:info(?MODULE, "~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]), + Err + end. -get_chain(Tab, Feed) -> - [case kvs:get(Tab, Id) of {ok, E} -> E; _ -> [] end || - Id <- lists:usort(dirty_foldl(valid_chains, [], {Tab, Feed}))]. +restore(File) -> restore(File, node(), []). +restore(File, Node, SkipTbs) -> + 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]), + {error, Reason}; + {atomic, Tabs} -> {ok, Tabs}; + ok -> ok end. remigrate_default_contact_settings() -> kvs:delete(schema_migrations, 20181008144317), - roster:migrate(). \ No newline at end of file + roster:migrate(). + +%% Subscriptions control + +unsub(<<"emqttd_", _/binary>>, _)-> ok; +unsub(<<"sys_", _/binary>>, _)-> ok; +unsub(_, <<"events/+/", _/binary>>)-> ok; +unsub(ClientId, T)->n2o_vnode:unsubscribe(ClientId,T). + +unsubscribe({all,Topic})-> + [n2o_vnode:unsubscribe(ClientId,T)|| + #mqtt_subproperty{key = {T, ClientId}} <- kvs:all(mqtt_subproperty), T==Topic]; + +unsubscribe(Topic)-> + [unsub(ClientId,T)|| + #mqtt_subproperty{key = {T, ClientId}} <- kvs:all(mqtt_subproperty), T==Topic]. + + diff --git a/apps/roster/src/roster_io.erl b/apps/roster/src/roster_io.erl index b6bf35a16761fdbbe4c4452e15b6db4050f2aea6..c1aa41c1fbe18e82a0cf1875e4edb58348e29f4c 100644 --- a/apps/roster/src/roster_io.erl +++ b/apps/roster/src/roster_io.erl @@ -15,10 +15,14 @@ warning(Module, String, Args) -> io:format(format_message(Module, String), Args). error(Module, String, Args) -> - io:format(format_message(Module, String), Args). + io:format(format_message(error, Module, String), to_one_row(Args)). format_message(Module, String) -> - nitro:to_list([roster:timestamp_to_datetime(roster:now_msec()), "::", Module, "::", String, "~n"]). + format_message([], Module, String). +format_message(error, Module, String) -> + format_message("!-!-!-!-!-!-!-!-ERROR-!-!-!-!-!-!-!-!", 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. diff --git a/apps/roster/src/roster_proto.erl b/apps/roster/src/roster_proto.erl index d4011cd1637c35fe63784723cd66ff1383f43bae..cfd94d05b6ac8aa64c33c26f5ae052717b8cbfec 100644 --- a/apps/roster/src/roster_proto.erl +++ b/apps/roster/src/roster_proto.erl @@ -1,6 +1,5 @@ -module(roster_proto). -include("roster.hrl"). -%-include_lib("bpe/include/bpe.hrl"). -include_lib("n2o/include/n2o.hrl"). -compile({parse_transform, bert_javascript}). -compile({parse_transform, bert_swift}). @@ -8,33 +7,26 @@ info(#'Job'{} = Job, Req, #cx{params = <<"emqttd_",_/binary>>} = State) -> roster_bpe :info(Job, Req, State); info(#'Typing'{} = Typing, Req, #cx{params = <<"emqttd_",_/binary>>} = State) -> roster_message :info(Typing, Req, State); -info(#'Message'{} = Message, Req, #cx{params = <<"sys_",_/binary>>} = State) -> roster_message :info(Message, Req, State); -info(#'History'{feed = #mqi{}} = History, Req, #cx{params = <<"emqttd_",_/binary>>} = State) -> roster_channel_history :info(History, Req, State); +info(#'Message'{} = Message, Req, #cx{params = <<"emqttd_" ,_/binary>>} = State) -> roster_message :info(Message, Req, State); +info(#'Message'{} = Message, Req, #cx{params = <<"sys_" ,_/binary>>} = State) -> roster_message :info(Message, Req, State); +%info(#'History'{feed = #mqi{}} = History, Req, #cx{params = <<"emqttd_",_/binary>>} = State) -> roster_channel_history :info(History, Req, State); info(#'History'{feed = #act{}} = History, Req, #cx{params = <<"emqttd_",_/binary>>} = State) -> roster_bpe :info(History, Req, State); info(#'History'{} = History, Req, #cx{params = <<"emqttd_",_/binary>>} = State) -> roster_history :info(History, Req, State); +info(#'Profile'{} = Profile, Req, #cx{params = <<"sys_" ,_/binary>>} = State) -> micro_profile :info(Profile, Req, State); info(#'Profile'{} = Profile, Req, #cx{params = <<"emqttd_",_/binary>>} = State) -> roster_profile :info(Profile, Req, State); +info(#'Roster'{} = Roster, Req, #cx{params = <<"sys_", _/binary>>} = State) -> micro_roster :info(Roster, Req, State); info(#'Roster'{} = Roster, Req, #cx{params = <<"emqttd_",_/binary>>} = State) -> roster_roster :info(Roster, Req, State); info(#'Friend'{} = Friend, Req, #cx{params = <<"emqttd_",_/binary>>} = State) -> roster_friend :info(Friend, Req, State); info(#'Search'{} = Search, Req, #cx{params = <<"emqttd_",_/binary>>} = State) -> roster_search :info(Search, Req, State); -info(#'Room'{type = channel} = Room, Req, #cx{params = <<"emqttd_",_/binary>>} = State) -> roster_channel :info(Room, Req, State); +info(#'Room'{} = Room, Req, #cx{params = <<"sys_" ,_/binary>>} = State) -> roster_room :info(Room, Req, State); +info(#'Room'{} = Room, Req, #cx{params = <<"emqttd_",_/binary>>} = State) -> roster_room :info(Room, Req, State); +info(#'Member'{} = Room, Req, #cx{params = <<"emqttd_",_/binary>>} = State) -> roster_room :info(Room, Req, State); info(#'Star'{} = Star, Req, #cx{params = <<"emqttd_",_/binary>>} = State) -> roster_favorite :info(Star, Req, State); info(#'ExtendedStar'{} = ExtendedStar, Req, #cx{params = <<"emqttd_",_/binary>>} = State) -> roster_favorite :info(ExtendedStar, Req, State); -info(#'Link'{} = Link, Req, #cx{params = <<"emqttd_",_/binary>>} = State) -> roster_channel_link :info(Link, Req, State); +info(#'Link'{} = Link, Req, #cx{params = <<"emqttd_",_/binary>>} = State) -> roster_link :info(Link, Req, State); +info(#'Auth'{} = Auth, Req, #cx{params = <<"sys_" ,_/binary>>} = State) -> micro_auth :info(Auth, Req, State); info(#'Auth'{} = Auth, Req, State) -> roster_auth :info(Auth, Req, State); -%% TBD is it OK -%% Extra routing - is it channel or ordinary room data - -info(#'Room'{id = RoomId} = Data, Req, #cx{params = <<"emqttd_",_/binary>>} = State) -> - case roster_channel_helper:is_channel(#muc{name = RoomId}) of true -> roster_channel:info(Data, Req, State); _ -> roster_room:info(Data, Req, State) end; - -info(#'Member'{id = MemId, feed_id = FeedId} = Data, Req, #cx{params = <<"emqttd_",_/binary>>} = State) -> - ResFeedId = case FeedId of [] -> roster_channel_helper:member_get_feed(MemId); _ -> FeedId end, - case roster_channel_helper:is_channel(ResFeedId) of true -> roster_channel_member:info(Data, Req, State); _ -> roster_room:info(Data, Req, State) end; - -info(#'Message'{feed_id = FeedId} = Data, Req, #cx{params = <<"emqttd_",_/binary>>} = State) -> - case roster_channel_helper:is_channel(FeedId) of true -> roster_channel_message:info(Data, Req, State); _ -> roster_message:info(Data, Req, State) end; - info(Message, Req, State) -> roster:info(?MODULE, "UNKNOWN:~p", [Message]), {unknown, Message, Req, State}. \ No newline at end of file diff --git a/apps/roster/src/roster_validator.erl b/apps/roster/src/roster_validator.erl new file mode 100644 index 0000000000000000000000000000000000000000..6285b5c53dc771dac438ae55fd135786b2189e4d --- /dev/null +++ b/apps/roster/src/roster_validator.erl @@ -0,0 +1,973 @@ +-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_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/main_var.hrl"). +-include_lib("roster/include/static/rest_text.hrl"). +-include_lib("roster/include/static_auth.hrl"). +-compile(export_all). + +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; + +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) -> 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 -> 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 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' -> 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, 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; + {type,_} when Type==[] orelse Type=='group' orelse Type=='channel' -> Acc2; + {status,_} when Status==[] 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' -> 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=='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, #'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') -> 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' -> 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 is_list(Data) -> + lists:foldl(fun(Tmp, Acc3) when 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' -> 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' -> 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) -> Acc2; + {proc,_} when Proc==[] orelse is_integer(Proc) -> Acc2; + {data,_} when 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') -> 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 = #'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 diff --git a/apps/roster/src/test/call_room_test.erl b/apps/roster/src/test/call_room_test.erl new file mode 100644 index 0000000000000000000000000000000000000000..798be28daef5250596bc4d0110990e845db51225 --- /dev/null +++ b/apps/roster/src/test/call_room_test.erl @@ -0,0 +1,22 @@ +-module(call_room_test). + +-include("roster.hrl"). + +-export([ + description/0, + create_room/0 +]). + +description() -> "Call Room Manipulation". + +create_room() -> +%% create call room with no members inside + {PhoneNumber, ClientId, SessionToken} = auth_test:fake_sign_up(), + roster_client:start_cli_receive(ClientId, SessionToken), + roster_client:start_client(?SYS_REST_CLIENT), + RoomId = iolist_to_binary(["CallRoom-", nitro:to_binary(roster:now_msec())]), + {ok, #'Member'{phone_id = AdmPhId} = AdminMember} = roster:to_member(PhoneNumber, admin), + #'Room'{id = RoomId, status = create, type = call, admins = [#'Member'{phone_id = AdmPhId, status = admin}], members = []} = + roster_client:send_receive(?SYS_REST_CLIENT, 1, #'Room'{id = RoomId, status = create, type = call, name = RoomId, admins = [AdminMember]}), + [roster_client:stop_client(CliId) || CliId <- [?SYS_REST_CLIENT, ClientId]], + ok. \ No newline at end of file diff --git a/apps/roster/src/test/job_tests.erl b/apps/roster/src/test/job_tests.erl index 21c8f57b42af032ea78ba554f1de63674fa82d68..a05e0f44113fbf87fe5c4b9ba62eb0f9054a467e 100644 --- a/apps/roster/src/test/job_tests.erl +++ b/apps/roster/src/test/job_tests.erl @@ -6,7 +6,6 @@ -include("roster.hrl"). -include("roster_test.hrl"). --define(MAX_ATTEMPTS, 3). %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% %% Job/delete diff --git a/apps/roster/src/test/main_test.erl b/apps/roster/src/test/main_test.erl index 4d0ee3ac8772d4b15e73f4d822a7af320065a351..d9d3166d250a5575e20c047c927b60ecf87ad352 100644 --- a/apps/roster/src/test/main_test.erl +++ b/apps/roster/src/test/main_test.erl @@ -1,8 +1,9 @@ -module(main_test). +-compile(export_all). -include("roster.hrl"). -include("roster_test.hrl"). --export([remove_fake_users_data/0]). + is_test_session(Features) -> case lists:keyfind(?TEST_SESSION_FK, #'Feature'.key, Features) of @@ -15,3 +16,240 @@ remove_fake_users_data() -> roster:info(?MODULE, "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]). + +muc_msgs() -> + RoomName = Room = <<"test_room_777">>, + Phones = [{APhone = <<"73777">>, admin}, {<<"79773">>, admin}, {<<"173737">>, member}, {<<"273747">>, member}], +%% Room2 = get_room_id(APhone, RoomName), + Counter = length(Phones), + roster:purge_room(Room), + Feed = #muc{name = Room}, + PCs = [{APhoneId, AClientId, _} | [{BPhoneId, BClientId, _}, {CPhoneId, CClientId, _}, {DPhoneId, DClientId, _} | _] = TPCs] = + [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), + Member = #'Member'{presence = online, feed_id = Feed, phone_id = PhoneId, status = Aff}, + {PhoneId, ClientId, Member} + end || {Phone, Aff} <- Phones], + _ = [ClientId || {_, ClientId, _, _} <- PCs], + {Admins, Members} = roster:split_members([M || {_, _, M} <- TPCs]), + roster_client:set_filer([AClientId, CClientId], filter_friend), + roster_client:send_receive(AClientId, 2, #'Friend'{phone_id = APhoneId, friend_id = CPhoneId, status = request}), + roster_client:send_receive(CClientId, 3, #'Friend'{phone_id = CPhoneId, friend_id = APhoneId, status = confirm}), + roster_client:set_filer([AClientId, CClientId], filter), + #'Room'{status = create, id = Room, + admins = [#'Member'{id = AId, status = admin, presence = online, phone_id = APhoneId}, + #'Member'{id = BId, status = admin, presence = online, phone_id = BPhoneId}], + members = [#'Member'{id = CId, status = member, presence = online, phone_id = CPhoneId}, + #'Member'{id = DId, status = member, presence = online, phone_id = DPhoneId}], type = group} + = roster_client:send_receive(AClientId, Counter, + #'Room'{status = create, type = group, id = Room, name = RoomName, admins = Admins, members = Members}), + Counter = 2 + length(emqttd_server:subscribers(roster:muc_topic(APhoneId))), + + #'Message'{id = FirstMentionId, mentioned = [CId]} = roster_client:send_receive(AClientId, Counter, + #'Message'{msg_id= <> , feed_id = #muc{name = Room}, from = APhoneId, to = Room, mentioned = [CId], + files = [#'Desc'{id = <<"6">>, payload = <<"Test MUC member mention C!">>}], status = []}), + + + #'Room'{mentions = [FirstMentionId]} = roster:room(roster:roster_id(CPhoneId), Room), + + #'Ack'{} = roster_client:send_receive(AClientId, #'Message'{msg_id= <> , feed_id = #muc{name = Room}, from = APhoneId, to = Room, mentioned = [CId], + files = [#'Desc'{id = <<"6">>, payload = <<"Test MUC member mention C!">>}], status = []}), + +% Message from C + #'Message'{id = FId} = roster_client:send_receive(CClientId, Counter, + #'Message'{msg_id= <> , feed_id = #muc{name = Room}, from = CPhoneId, to = Room, + files = [#'Desc'{id = <<"7">>, payload = <<"Test MUC member C!">>}], status = []}), + {ok, #'Room'{readers = [CId, _AId]}} = kvs:get('Room', Room), %%TODO insert reader +% Message from B + #'Message'{id = BFid} = roster_client:send_receive(BClientId, Counter, + #'Message'{msg_id = <>,feed_id = #muc{name = Room}, from = BPhoneId, to = Room, files = [#'Desc'{id = <<"7">>, payload = <<"TestB!">>}], status = []}), + {ok, #'Room'{readers = [BId, CId]}} = kvs:get('Room', Room), + #'Roster'{roomlist = [#'Room'{last_msg = #'Message'{id = BFid}}]} = roster:roster(roster:roster_id(APhoneId)), + #'History'{} = roster_client:send_receive(DClientId, + #'History'{feed = #muc{name = Room}, roster_id = DPhoneId, size = 20, entity_id=0, status = get}), + roster_client:send_receive(DClientId, last_res), + roster_client:send_receive(DClientId, Counter + 1, #'History'{feed = #muc{name = Room}, roster_id = DPhoneId, entity_id = BFid, status = update}), + {ok, #'Room'{readers = [BId, DId]}} = kvs:get('Room', Room), + [#'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}, + 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}, + from=BPhoneId, files = [#'Desc'{id= <<"7">>, payload = <<"Edited by B!">>}], status = edit}), + +%% Update with msg_id + D = #'Desc'{id = <<"8">>, payload = <<"Тест A2!">>, mime = <<"translate">>, + data = [#'Feature'{id = ?LANG_KEY, key = ?LANG_KEY, value = <<"ru">>, group = <<"FILE_DATA">>}]}, +% Message from B is updated + #'Message'{files = [#'Desc'{mime = <<"text">>}, #'Desc'{mime = <<"translate">>}]} = roster_client:send_receive(BClientId, Counter, + #'Message'{id = BFid, msg_id = <>, feed_id = #muc{name = Room}, from = BPhoneId, to = Room, + files = [D#'Desc'{payload = <<"Тест B!">>}], status = update}), + + #'Message'{files = [#'Desc'{mime = <<"text">>}]} = roster_client:send_receive(BClientId, Counter, + #'Message'{id = BFid, feed_id = #muc{name = Room}, from = BPhoneId, to = Room, + files = [D#'Desc'{payload = [], data = []}], status = update}), + + #'Message'{files = [#'Desc'{mime = <<"text">>}, #'Desc'{mime = <<"translate">>}]} = roster_client:send_receive(BClientId, Counter, + #'Message'{id = BFid, feed_id = #muc{name = Room}, from = BPhoneId, to = Room, + files = [D#'Desc'{payload = <<"Тест B!">>}], status = update}), + + #'Message'{files = [#'Desc'{}, #'Desc'{mime = <<"translate">>, data = Data2}]} + = roster_client:send_receive(AClientId, Counter, + #'Message'{id = BFid, msg_id = <>, feed_id = #muc{name = Room}, from = APhoneId, to = Room, + files = [D#'Desc'{payload = <<"Тест A!">>}], status = update}), + #'Feature'{value = I} = roster:get_data(?USERS_KEY, Data2), + + #'Message'{files = [#'Desc'{}, #'Desc'{mime = <<"translate">>, data = Data3, payload = <<"Test A2!">>}]} + = roster_client:send_receive(AClientId, Counter, + #'Message'{id = BFid, feed_id = #muc{name = Room}, from = APhoneId, to = Room, + files = [D#'Desc'{payload = <<"Test A2!">>}], status = update}), + + #'Feature'{value = I} = roster:get_data(?USERS_KEY, Data3), + + #'Message'{files = Ds = [#'Desc'{}, #'Desc'{}, #'Desc'{mime = <<"translate">>, payload = <<"Test A3!">>, data = Data4}]} + = roster_client:send_receive(AClientId, Counter, + #'Message'{id = BFid, feed_id = #muc{name = Room}, from = APhoneId, to = Room, + files = [roster:set_data(#'Feature'{key = ?LANG_KEY, value = <<"en">>}, + D#'Desc'{id = <<"9">>, payload = <<"Test A3!">>})], status = update}), +% + #'Feature'{value = <<"en">>} = roster:get_data(?LANG_KEY, Data4), + + [[PhoneId] = lists:flatten([[P || P <- binary:split((roster:get_data(?USERS_KEY, TData))#'Feature'.value, <<",">>, [global]), P == PhoneId] + || #'Desc'{data = TData, mime = <<"translate">>} <- Ds, (roster:get_data(?USERS_KEY, TData))#'Feature'.value /= []]) || PhoneId <- [APhoneId, BPhoneId]], +% A delete first message from C + #'Message'{seenby = [-1]} = + roster_client:send_receive(AClientId, Counter, #'Message'{id = FId, msg_id= <>, feed_id = #muc{name = Room}, from = APhoneId, to = Room, + seenby = [-1], status = delete}), + + #'Ack'{} = + roster_client:send_receive(AClientId, #'Message'{id = FId, msg_id= <>, feed_id = #muc{name = Room}, from = APhoneId, to = Room, + seenby = [-1], status = delete}), + + +% A updates message from B + #'Message'{files = Ds2 = [#'Desc'{}, #'Desc'{}, #'Desc'{}, #'Desc'{mime = <<"posttranslate">>, data = Data5}]} + = roster_client:send_receive(AClientId, Counter, + #'Message'{id = BFid, feed_id = #muc{name = Room}, from = APhoneId, to = Room, + files = [roster:set_data(#'Feature'{key = ?LANG_KEY, value = <<"gb">>}, + D#'Desc'{id = <<"10">>, payload = <<"POST TRANSLATE">>, mime = <<"posttranslate">>})], status = update}), +% + #'Feature'{value = <<"gb">>} = roster:get_data(?LANG_KEY, Data5), + + [[PhoneId] = lists:flatten([[P || P <- binary:split((roster:get_data(?USERS_KEY, TData))#'Feature'.value, <<",">>, [global]), P == PhoneId] + || #'Desc'{data = TData, mime = <<"posttranslate">>} <- Ds2, (roster:get_data(?USERS_KEY, TData))#'Feature'.value /= []]) || PhoneId <- [APhoneId]], + + #'Message'{files = Ds3 = [#'Desc'{}, #'Desc'{}, #'Desc'{}, #'Desc'{mime = <<"posttranslate">>, data = Data6}]} + = roster_client:send_receive(AClientId, Counter, + #'Message'{id = BFid, feed_id = #muc{name = Room}, from = APhoneId, to = Room, + files = [roster:set_data(#'Feature'{key = ?LANG_KEY, value = <<"en">>}, + D#'Desc'{id = <<"11">>, payload = <<"POST TRANSLATE">>, mime = <<"posttranslate">>})], status = update}), + + #'Feature'{value = <<"en">>} = roster:get_data(?LANG_KEY, Data6), + + [[PhoneId] = lists:flatten([[P || P <- binary:split((roster:get_data(?USERS_KEY, TData))#'Feature'.value, <<",">>, [global]), P == PhoneId] + || #'Desc'{data = TData, mime = <<"posttranslate">>} <- Ds3, (roster:get_data(?USERS_KEY, TData))#'Feature'.value /= []]) || PhoneId <- [APhoneId]], + + #'Message'{files = Ds4 = [#'Desc'{}, #'Desc'{}, #'Desc'{}, #'Desc'{mime = <<"posttranslate">>, data = Data7}]} + = roster_client:send_receive(BClientId, Counter, + #'Message'{id = BFid, feed_id = #muc{name = Room}, from = APhoneId, to = Room, + files = [roster:set_data(#'Feature'{key = ?LANG_KEY, value = <<"en">>}, + D#'Desc'{id = <<"12">>, payload = <<"POST TRANSLATE">>, mime = <<"posttranslate">>})], status = update}), + #'Feature'{value = <<"en">>} = roster:get_data(?LANG_KEY, Data7), + + [[PhoneId] = lists:flatten([[P || P <- binary:split((roster:get_data(?USERS_KEY, TData))#'Feature'.value, <<",">>, [global]), P == PhoneId] + || #'Desc'{data = TData, mime = <<"posttranslate">>} <- Ds4, (roster:get_data(?USERS_KEY, TData))#'Feature'.value /= []]) || PhoneId <- [APhoneId, BPhoneId]], + + + #'Message'{files = Ds5 = [#'Desc'{}, #'Desc'{}, #'Desc'{}]} + = roster_client:send_receive(AClientId, Counter, + #'Message'{id = BFid, feed_id = #muc{name = Room}, from = APhoneId, to = Room, + files = [D#'Desc'{id = <<"9">>, payload = [], data = []}], status = update}), + + [[] = lists:flatten([[P || P <- binary:split((roster:get_data(?USERS_KEY, TData))#'Feature'.value, <<",">>, [global]), P == PhoneId] + || #'Desc'{data = TData, mime = <<"translate">>} <- Ds5, (roster:get_data(?USERS_KEY, TData))#'Feature'.value /= []]) || PhoneId <- [APhoneId]], + + roster_client:send(DClientId, #'History'{feed = #muc{name = Room}, roster_id = DPhoneId, entity_id = BFid, status = update}), + {ok, #'Room'{last_msg = BFid}} = kvs:get('Room', Room), + #'Roster'{roomlist = [#'Room'{last_msg = #'Message'{id = BFid}}]} + = roster:roster(roster:roster_id(BPhoneId)), + + roster_client:send_receive(CClientId, last_res), + roster_client:send(CClientId, #'History'{feed = #muc{name = Room}, roster_id = CPhoneId, entity_id = FId, status = update}), + [] = roster_client:send_receive(CClientId, last_res), +% Message from A + % #'Room'{}=roster_client:send_receive(CClientId,#'History'{feed=#muc{name = Room}, roster_id=CPhoneId, entity_id = FId, status=update}), + #'Message'{} = roster_client:send_receive(AClientId, Counter, #'Message'{feed_id = #muc{name = Room}, from = APhoneId, to = Room, + files = [#'Desc'{id = <<"7">>, payload = <<"TestA!">>}], status = []}), +% + #'History'{} = roster_client:send_receive(CClientId, + #'History'{feed = #muc{name = Room}, roster_id = CPhoneId, size = [], status = get}), + timer:sleep(1000), +%% #'Message'{} = roster_client:send_receive(BClientId, Counter, +%% #'Message'{feed_id = #muc{name = Room}, from = BPhoneId, files = [#'Desc'{payload = <<"TestB!">>}], status = delete}), + + #'Message'{} = roster_client:send_receive(BClientId, + #'History'{feed = #muc{name = Room}, roster_id = BPhoneId, status = delete}), + + #'Message'{id = Id} = + roster_client:send_receive(CClientId, Counter, + #'Message'{feed_id = #muc{name = Room}, from = CPhoneId, to = Room,files = [#'Desc'{id = <<"7">>, payload = <<"Test MUC member C!">>}], status = []}), + #'Message'{id = Id2} = + roster_client:send_receive(BClientId, Counter, + #'Message'{feed_id = #muc{name = Room}, from = BPhoneId, to = Room, files = [#'Desc'{id = <<"7">>, payload = <<"New TestB!">>}], status = []}), + roster_client:send_receive(CClientId, last_res), + #'Message'{} = + roster_client:send_receive(CClientId, Counter, + #'Message'{id=Id, feed_id = #muc{name = Room}, from = CPhoneId, to = Room, files = [#'Desc'{id = <<"7">>, payload = <<"Edit Test MUC member C!">>}], status = edit}), + roster_client:send_receive(CClientId, Counter + 1, #'History'{feed = #muc{name = Room}, roster_id = CPhoneId, entity_id = Id2, status = update}), + [#'Message'{id = Id2, type = [read]}, #'Room'{}] = roster_client:send_receive(CClientId, last_res), + #'Message'{id = Id3} = + roster_client:send_receive(AClientId, Counter, + #'Message'{feed_id = #muc{name = Room}, from = APhoneId, to = Room, + files = [#'Desc'{id = <<"7">>, payload = <<"TestA!">>}], status = []}), +%% Test cases for history +% A gets history from Id to position -4 + #'History'{data=[#'Message'{id = Id}, #'Message'{}]} + = roster_client:send_receive(AClientId, #'History'{feed = #muc{name = Room}, roster_id = APhoneId, entity_id = Id, size = -4, status = get}), +% C gets history from Id to position +10 + #'History'{data=[#'Message'{id = Id3}|_]} = roster_client:send_receive(CClientId, + #'History'{feed = #muc{name = Room}, roster_id = CPhoneId, size = 10, status = get}), +% B gets all history from Id + #'History'{} = roster_client:send_receive(BClientId, #'History'{feed = #muc{name = Room}, roster_id =BPhoneId, entity_id = Id, size=[], status = get}), +% B gets full history + #'History'{data=[#'Message'{id = Id3},#'Message'{},#'Message'{id = Id2},#'Message'{id = Id}, #'Message'{}]} = roster_client:send_receive(BClientId, #'History'{feed = #muc{name = Room}, roster_id =BPhoneId, size=[], status = get}), +% D gets history from Id to position +3 + #'History'{} = roster_client:send_receive(DClientId, + #'History'{feed = #muc{name = Room}, roster_id = DPhoneId, entity_id = Id, size = 3, status = get}), + + #'Message'{seenby = [-1], link = Id3} = + roster_client:send_receive(AClientId, Counter, #'Message'{id = Id3, from = APhoneId, to = Room, feed_id = #muc{name = Room}, seenby = [-1], status = delete}), + #io{} = + roster_client:send_receive(AClientId, #'Message'{id = Id3, from = APhoneId, to = Room, feed_id = #muc{name = Room}, seenby = [DPhoneId], status = delete}), + {ok, #'Room'{last_msg = Id2}} = kvs:get('Room', Room), + #'Message'{id = MentionId, mentioned = [CId]} = + roster_client:send_receive(AClientId, Counter, #'Message'{from = APhoneId, to = Room, feed_id = #muc{name = Room}, + files = [#'Desc'{id = <<"8">>, payload = <<"TestMentions!">>}], + mentioned = [CId]}), + + #'Roster'{roomlist = [#'Room'{admins = [#'Member'{id = AId}], last_msg = #'Message'{id = MentionId, status = []}, mentions = [MentionId]}]} + = roster:roster(roster:roster_id(CPhoneId)), + #'Roster'{roomlist = [#'Room'{last_msg = #'Message'{id = MentionId, status = []}, mentions = []}]} + = roster:roster(roster:roster_id(BPhoneId)), + + #'Message'{id = LastMsgId2} = roster_client:send_receive(CClientId, Counter, + #'Message'{feed_id = #muc{name = Room}, from = CPhoneId, to = Room, files = [#'Desc'{id = <<"71">>, payload = <<"Test MUC member delete last!">>}], status = []}), + + #'Message'{seenby = [-1]} = + roster_client:send_receive(CClientId, Counter, + #'Message'{id = LastMsgId2, feed_id = #muc{name = Room}, from = CPhoneId, to = Room, + seenby = [-1], status = delete}), + + #'Roster'{roomlist = [#'Room'{last_msg = #'Message'{id = LMId2, status = update}}]} = + roster:roster(roster:roster_id(CPhoneId)), + true = LastMsgId2 > LMId2, + [roster_client:stop_client(ClientId) || {_, ClientId, _} <- PCs], ok. diff --git a/apps/roster/src/test/micro_test.erl b/apps/roster/src/test/micro_test.erl new file mode 100644 index 0000000000000000000000000000000000000000..7f1bf323ba6acfb5265e0661868384f7dd878f9e --- /dev/null +++ b/apps/roster/src/test/micro_test.erl @@ -0,0 +1,258 @@ +-module(micro_test). +-compile(export_all). +-include_lib("n2o/include/n2o.hrl"). +-include("roster.hrl"). +-include("micro.hrl"). +-include("roster_test.hrl"). + +start_client(Name) -> start_client(Name, <<>>). +start_client(Name, Token) -> roster_client:start_client(Name, Token, [{username, <<"micro">>}]). + + +start_cli_receive(Name, Token) -> start_cli_receive(Name, Token, 0). +start_cli_receive(Name, Token, Counter) -> roster_client:start_cli_receive(Name, Token, Counter, [{host, ?HOST}, {username, <<"micro">>}]). + +test_micro() -> + APhone = <<"MICRO">>, + Token = <<"valid,">>, + Phones = [APhone], + micro:purge_user(APhone), + [{APhoneId, AClientId}] = + [begin + {ClientId, Token2} = roster_client:reg_fake_user(Phone), + roster_client:start_cli_receive(ClientId, Token2), + [PhoneId] = roster_client:rosters(ClientId, Phone), {PhoneId, ClientId} + end || Phone <- Phones], + roster_client:stop_client(AClientId), + micro:link_user(APhone), + [#'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), + OldModule = application:get_env(roster, validate_token, ?MODULE), + application:set_env(roster, validate_token, ?MODULE), + {ok, ALinkedAIdBin} = validate_token(<>), + #'Profile'{phone = APhone} = start_cli_receive(AClientId, <>), + application:set_env(roster, validate_token, OldModule), + roster_client:stop_client(AClientId), + ok. + +test_account() -> + BridgeClient = ?SYS_BRIDGE_TEST_CLIENT, + APhone = <<"MICRO-ACC">>, + APhoneUuid = micro:norm_uuid(ABinPhoneUuid = micro:to_uuid(phone, APhone)), + TokenPattern = <<"valid,">>, + kvs:put(#'Whitelist'{phone = APhoneUuid, created = roster:now_msec()}), + OldModule = application:get_env(roster, validate_token, ?MODULE), + micro:purge_user(APhoneUuid), + AClientId = roster_client:gen_name(APhone), + AClientId2 = roster_client:gen_name(APhone, "DevKey2"), + AClientIds = [AClientId, AClientId2], + [[] = ets:lookup(mqtt_subscriber, roster:action_topic(ClientId)) || ClientId<- AClientIds], + + roster_client:start_client(BridgeClient), + #'Profile'{status = create} = roster_client:send_receive(BridgeClient, #'Profile'{phone = APhoneUuid, status = create}), + {ok, #'Profile'{phone = APhoneUuid}} = kvs:get('Profile', APhoneUuid), + {ok, #'LinkProfile'{id = ABinPhoneUuid, phone = APhoneUuid}} = kvs:get('LinkProfile', ABinPhoneUuid), + #'Roster'{status = add} = roster_client:send_receive(BridgeClient, + #'Roster'{id = micro:norm_uuid(uuid:uuid4()), phone = APhoneUuid, status = add}), + + {ok, #'Profile'{rosters = [RosterId]}} = kvs:get('Profile', APhoneUuid), + APhoneId = roster:phone_id(APhoneUuid, RosterId), + {ok, #'Roster'{userlist = [#'Contact'{phone_id = APhoneId}], status = []}} + = kvs:get('Roster', RosterId), + [#'LinkRoster'{id = ARosterLnkId, phone_id = APhoneId}] = kvs:index('LinkRoster', phone_id, roster:phone_id(APhoneUuid, RosterId)), + + application:set_env(roster, validate_token, ?MODULE), + {ok, AAccId} = validate_token(<>), + #'Profile'{phone = APhoneUuid, rosters = [#'Roster'{id = ARosterId, phone = APhoneUuid}]} + = start_cli_receive(AClientId, Token2 = <>), + {ok, #'Roster'{phone = APhoneUuid}} = kvs:get('Roster', ARosterId), + {ok, #'Auth'{phone = APhoneUuid, user_id = APhoneId}} = kvs:get('Auth', AClientId), + Settings = [#'Feature'{id = <<"dummy_id">>, key = <<"dummy_key">>}], + #'Auth'{type = update, token = Token2, settings = Settings} + = roster_client:send_receive(BridgeClient, 2, #'Auth'{client_id = AClientId, token = Token2, settings = Settings, type = update}), + {ok, #'Auth'{settings = Settings, token = Token2, type = verified}} = kvs:get('Auth', AClientId), + [roster_client:send_receive(ClientId, last_res) || ClientId <- [BridgeClient, AClientId]], + #'Profile'{status = update} = roster_client:send_receive(BridgeClient, 2, #'Profile'{phone = APhoneUuid, status = update, settings = Settings}), + [[#'Profile'{status = update, phone = APhoneUuid}] = roster_client:send_receive(ClientId, last_res) + || ClientId <- [BridgeClient, AClientId]], + + [Names, Surnames] = [<<"Leonard">>, <<"Messi">>], + [_] = ets:lookup(mqtt_subscriber, roster:action_topic(BridgeClient)), + + roster_client:send_receive(BridgeClient, last_res), + #'Roster'{status = update, names = Names, surnames = Surnames} + = roster_client:send_receive(BridgeClient, 1, + #'Roster'{status = update, id = micro:norm_uuid(ARosterLnkId), names = Names, surnames = Surnames}), + [#'Roster'{status = update}] = roster_client:send_receive(BridgeClient, last_res), + {ok, #'Roster'{names = Names, surnames = Surnames, status = []}} + = kvs:get('Roster', roster:roster_id(APhoneId)), + + #'Profile'{phone = APhoneUuid, rosters = [#'Roster'{id = ARosterId, phone = APhoneUuid}]} + = start_cli_receive(AClientId2, Token2 = <>), + roster_client:receive_drop(), + #'Auth'{client_id = AClientId, type = logout} + = roster_client:send_receive(BridgeClient, #'Auth'{client_id = AClientId, type = logout}), + {error, _} = kvs:get('Auth', AClientId), + {ok, _} = kvs:get('Auth', AClientId2), + [] = ets:lookup(mqtt_subscriber, roster:action_topic(AClientId)), + [_] = ets:lookup(mqtt_subscriber, roster:action_topic(AClientId2)), + application:set_env(roster, validate_token, OldModule), + timer:sleep(1000), + [roster_client:stop_client(ClientId) || ClientId<-[BridgeClient|AClientIds]], + ok. + +test_account2() -> + BridgeClient = ?SYS_BRIDGE_TEST_CLIENT, + APhone = <<"MICRO-ACC">>, + APhoneUuid = micro:norm_uuid(ABinPhoneUuid = micro:to_uuid(phone, APhone)), + kvs:put(#'Whitelist'{phone = APhoneUuid, created = roster:now_msec()}), + micro:purge_user(APhoneUuid), + AClientId = roster_client:gen_name(APhone), + [] = ets:lookup(mqtt_subscriber, roster:action_topic(AClientId)), + + roster_client:start_client(BridgeClient), + #'Profile'{status = create} = roster_client:send_receive(BridgeClient, + #'Profile'{phone = APhoneUuid, rosters = [#'Roster'{id = micro:norm_uuid(uuid:uuid4()), phone = APhoneUuid}], status = create}), + {ok, #'Profile'{phone = APhoneUuid}} = kvs:get('Profile', APhoneUuid), + {ok, #'LinkProfile'{id = ABinPhoneUuid, phone = APhoneUuid}} = kvs:get('LinkProfile', ABinPhoneUuid), + + {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}] + = kvs:index('LinkRoster', phone_id, roster:phone_id(APhoneUuid, RosterId)), + [roster_client:stop_client(ClientId) || ClientId <- [BridgeClient, AClientId]], + ok. + + +suite() -> + [ + test_micro(), + test_account(), + test_account2() + ]. + +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] +. + +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}]), + 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(<>), + #'Profile'{phone = UUID, rosters = [#'Roster'{id = RosterId, phone = UUID}]} + = start_cli_receive(ClientId, Token2 = <>), + {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), + {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, + #'Roster'{id = micro:norm_uuid(uuid:uuid4()), phone = Auuid, status = add}), + {ok,#'LinkRoster'{phone_id = APId2}} = kvs:get('LinkRoster', micro:uuid_to_bin(Auuid2)), + ARId2=roster:roster_id(APId2), + {ok, #'Profile'{rosters = [AR,ARId2]}} = kvs:get('Profile', Auuid), + {ok, #'Roster'{}} = kvs:get('Roster', AR), +% APhoneId = roster:phone_id(APhoneUuid, RosterId), +% [#'LinkRoster'{id = ARosterLnkId, phone_id = APhoneId}] = kvs:index('LinkRoster', phone_id, roster:phone_id(APhoneUuid, RosterId)), + + [roster_client:send_receive(AClientId, 1, #'Friend'{phone_id = A, friend_id = To, status = request}) || To <- [B, C]], + [roster_client:send_receive(roster_client:gen_name(roster:phone(To)), 3, #'Friend'{phone_id = To, friend_id = A, status = confirm}) || To <- [B, C]], + roster_client:set_filer([AClientId, BClientId, CClientId], filter), + 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), + 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 = [ + #'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), + [roster_client:send(AClientId, #'Job'{time = roster:now_msec() +1000+ DT * 300, feed_id = {act, <<"publish">>, A}, data = + [#'Message'{feed_id = roster:feed_key(p2p, A, A), from = A, to = A, %type=forward, + files = + [#'Desc'{id = <<"7">>, payload = list_to_binary([<<"777">> | [integer_to_binary(rand:uniform(99999999)) || _ <- lists:seq(1, 70)]])}], + %<<"Next TestA!">> + status = []}], + status = init}) || DT <- lists:seq(1, 3)], +%% #'Job'{proc = Proc} = roster_client:send_receive(BClientId, #'Job'{time = roster:now_msec() + 7000, feed_id = {act, <<"publish">>, B}, data = [ +%% #'Message'{feed_id = roster:feed_key(p2p, A, B), from = B, to = A, files = [#'Desc'{id = <<"7">>, payload = <<"TestB!">>}], status = []}] +%% , status = init}), + #'Message'{id = EId} = roster_client:send_receive(BClientId, 2, #'Message'{feed_id = roster:feed_key(p2p, A, B), from = B, to = A, + files = [#'Desc'{id = <<"1">>, payload = <<"Opa 77777777777777">>}, + #'Desc'{id = <<"3">>, payload = <<"E TestA!">>}], status = []}), + timer:sleep(1000), + roster_client:receive_drop(), + %#'Job'{ proc=Proc1}= + #'Message'{} = roster_client:send_receive(BClientId,4, #'Job'{time = [], feed_id = {act, <<"publish">>, B}, data = [ + #'Message'{id = EId, feed_id = roster:feed_key(p2p, A, B), from = B, to = A, %type=forward, + files = + [#'Desc'{id = <<"7">>, payload = list_to_binary([<<"777">> | [integer_to_binary(rand:uniform(99999999)) || _ <- lists:seq(1, 7)]])}], + %<<"Next TestA!">> + status = edit}, #'Message'{feed_id = roster:feed_key(p2p, A, B), from = B, to = A, %type=forward, + files = + [#'Desc'{id = <<"7">>, payload = list_to_binary([<<"Opa">> | [integer_to_binary(rand:uniform(99999999)) || _ <- lists:seq(1, 7)]])}], + %<<"Next TestA!">> + status = []}] + , + status = init}), +% roster_client:receive_test(AClientId, #'Message'{}, 1), + timer:sleep(1000), + roster_client:receive_drop(), + #'History'{} = roster_client:send_receive(AClientId,1, #'History'{roster_id = A, feed = {act, <<"publish">>, A}, size = [], status = get}), + %#io{code=#complete{ },data=D} = roster_client:send_receive(AClientId, #'Job'{proc = [Id], data= <<"stop">>, status=update}), + %roster_client:send(AClientId, #'Job'{proc=Proc, status=update }), +%% #'Job'{id = UpId, proc = Proc} = roster_client:send_receive(BClientId, #'Job'{time = roster:now_msec() + 30000, feed_id = {act, <<"publish">>, B}, data = [ +%% #'Message'{feed_id = roster:feed_key(p2p, A, B), from = B, to = A, files = [#'Desc'{id = <<"7">>, payload = <<"Next 2 TestA!">>}], status = []}] +%% , status = init}), +%% roster_client:receive_drop(), + % timer:sleep(1000), +%% #'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)), + 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}) +%% || Room <- RoomNames], +%% [roster_client:send_receive(AClientId, Counter+1, #'Message'{feed_id = #muc{name = Room}, from = APhoneId, +%% files = [#'Desc'{id = <<"fake_id2">>, payload = +%% <<"Reverse Message in ", Room/binary, " from ", AClientId/binary>>}], status = []}) +%% || Room <- lists:reverse(RoomNames)], + roster_client:send(BridgeClient, #'Profile'{status = remove, phone = Auuid, rosters=[micro:norm_uuid(Auuid1Link),Auuid2]}), +% application:set_env(roster, job_delay, JobDelay), +%% roster_client:send_receive(AClientId, #'Job'{proc= 3, data =[#'Message'{from =A, to=B, status = []}], time=roster:now_msec()+10000 , status=restart}), + timer:sleep(4000), + [roster_client:stop_client(roster_client:gen_name(micro:norm_uuid(micro:to_uuid(phone, Phone)))) || {Phone,_} <- Phones],%--[APhone]], + ok. + + + +validate_token(<<"valid,", UserId/binary>>) -> {ok, UserId}; +validate_token(<<"expired,", UserId/binary>>) -> {expired, UserId}; +validate_token(_) -> error. \ No newline at end of file diff --git a/apps/roster/src/test/room_test.erl b/apps/roster/src/test/room_test.erl index d4694d021cc39b05d9c71dfb56e4c858ee51c32f..0b5a3d8dbf2d6df6ea6fdd35d9f3f601125dffbb 100644 --- a/apps/roster/src/test/room_test.erl +++ b/apps/roster/src/test/room_test.erl @@ -1,91 +1,229 @@ -module(room_test). - -include("roster.hrl"). +-include_lib("roster/include/static/roster_var.hrl"). -export([ - check/0, - check_alias_increment/0, - add_many_members/0 + check/0, + check_alias_increment/0, + add_many_members/0, + link/0, + test_joinlink/0 ]). +-define(BLANK_LINK, <<"UNDEFINED">>). check_alias_increment() -> %% check alias on group create and member update %% create a room with 2+ members. set the same alias. check is it incremented - TestClients = [ - {_, ClientId1, _}, - {_, ClientId2, _}, - {_, ClientId3, _} - ] = [auth_test:fake_sign_up() || _ <- lists:seq(0,2)], - [roster_client:start_cli_receive(ClientId, SessionToken) || {_, ClientId, SessionToken} <- TestClients], - TestAlias = "TestAlias", - RoomId = iolist_to_binary([<<"BETestRoom">>, nitro:to_binary(roster:now_msec())]), + TestClients = [ + {_, ClientId1, _}, + {_, ClientId2, _}, + {_, ClientId3, _} + ] = [auth_test:fake_sign_up() || _ <- lists:seq(0, 2)], + [roster_client:start_cli_receive(ClientId, SessionToken) || {_, ClientId, SessionToken} <- TestClients], + TestAlias = "TestAlias", + RoomId = iolist_to_binary([<<"BETestRoom">>, nitro:to_binary(roster:now_msec())]), %% create room with same aliases for all people inside - #'Room'{status = create, type = group, id = RoomId, - admins = [#'Member'{status = admin, alias = TestAlias, id = Adm}], - members = [#'Member'{status = member, alias = TestAlias, id = Mem1}, #'Member'{status = member, alias = TestAlias, id = Mem2}]} = - roster_client:send_receive(ClientId1, length(TestClients), - #'Room'{id = RoomId, - status = create, type = group, name = RoomId, - admins = [#'Member'{alias = TestAlias, phone_id = roster:phone_id(ClientId1), status = admin}], - members = [ - #'Member'{alias = TestAlias, phone_id = roster:phone_id(ClientId2), status = member}, - #'Member'{alias = TestAlias, phone_id = roster:phone_id(ClientId3), status = member}]}), + #'Room'{status = create, type = group, id = RoomId, + admins = [#'Member'{status = admin, alias = TestAlias, id = Adm}], + members = [#'Member'{status = member, alias = TestAlias, id = Mem1}, #'Member'{status = member, alias = TestAlias, id = Mem2}]} = + roster_client:send_receive(ClientId1, length(TestClients), + #'Room'{id = RoomId, + status = create, type = group, name = RoomId, + admins = [#'Member'{alias = TestAlias, phone_id = roster:phone_id(ClientId1), status = admin}], + members = [ + #'Member'{alias = TestAlias, phone_id = roster:phone_id(ClientId2), status = member}, + #'Member'{alias = TestAlias, phone_id = roster:phone_id(ClientId3), status = member}]}), %% update alias by one of member and the same alias for another one - TestAlias2 = "TestAlias2", - roster_client:send_receive(ClientId3, length(TestClients), #'Member'{id = Mem2, alias = TestAlias2, status = patch}), - roster_client:send_receive(ClientId1, length(TestClients), #'Member'{id = Adm, alias = TestAlias2, status = patch}), - #'Room'{id = RoomId, type = group, status = get, admins = [#'Member'{status = admin, alias = TestAlias2, id = Adm}], - members = [#'Member'{status = member, alias = TestAlias2, id = Mem2}, #'Member'{status = member, alias = TestAlias, id = Mem1}]} = - roster_client:send_receive(ClientId3, #'Room'{id = RoomId, status = get}), - [roster_client:stop_client(ClientId) || {_, ClientId, _} <- TestClients], - ok. + TestAlias2 = "TestAlias2", + roster_client:send_receive(ClientId3, length(TestClients), #'Member'{id = Mem2, alias = TestAlias2, status = patch}), + roster_client:send_receive(ClientId1, length(TestClients), #'Member'{id = Adm, alias = TestAlias2, status = patch}), + #'Room'{id = RoomId, type = group, status = get, admins = [#'Member'{status = admin, alias = TestAlias2, id = Adm}], + members = [#'Member'{status = member, alias = TestAlias2, id = Mem2}, #'Member'{status = member, alias = TestAlias, id = Mem1}]} = + roster_client:send_receive(ClientId3, #'Room'{id = RoomId, status = get}), + [roster_client:stop_client(ClientId) || {_, ClientId, _} <- TestClients], + ok. %% = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = to_member(List) -> to_member(List, member). to_member(List, Status) -> - lists:flatten([begin - [{PhoneId, FName, LName} | _] = roster:list_rosters(roster:phone(ClientId)), - #'Member'{phone_id = PhoneId, names = FName, surnames = LName, alias = <<>>, status = Status} - end || {_, ClientId, _} <- List]). + lists:flatten([begin + [{PhoneId, FName, LName} | _] = roster:list_rosters(roster:phone(ClientId)), + #'Member'{phone_id = PhoneId, names = FName, surnames = LName, alias = <<>>, status = Status} + end || {_, ClientId, _} <- List]). add_many_members() -> %% create group with 1 member %% add 10+ members to room - TestClients = [auth_test:fake_sign_up() || _ <- lists:seq(0, 15)], - [roster_client:start_cli_receive(ClientId, SessionToken) || {_, ClientId, SessionToken} <- TestClients], - RoomId = iolist_to_binary([<<"BETestRoom">>, nitro:to_binary(roster:now_msec())]), + TestClients = [auth_test:fake_sign_up() || _ <- lists:seq(0, 15)], + [roster_client:start_cli_receive(ClientId, SessionToken) || {_, ClientId, SessionToken} <- TestClients], + RoomId = iolist_to_binary([<<"BETestRoom">>, nitro:to_binary(roster:now_msec())]), %% choose admin member - {_, ClientId1, _} = lists:nth(1, TestClients), - - #'Room'{status = create, type = group, id = RoomId, admins = [#'Member'{status = admin}], members = [#'Member'{status = member}]} = - roster_client:send_receive(ClientId1, 2, - #'Room'{id = RoomId, - status = create, type = group, name = RoomId, - admins = to_member([lists:nth(1, TestClients)], admin), - members = to_member([lists:nth(2, TestClients)])}), - - #'Room'{status = add, type = group, id = RoomId, admins = [#'Member'{status = admin}], members = Members} = - roster_client:send_receive(ClientId1, 2, - #'Room'{id = RoomId, - status = add, type = group, - members = to_member(lists:nthtail(2, TestClients)) }), + {_, ClientId1, _} = lists:nth(1, TestClients), + + #'Room'{status = create, type = group, id = RoomId, admins = [#'Member'{status = admin}], members = [#'Member'{status = member}]} = + roster_client:send_receive(ClientId1, 2, + #'Room'{id = RoomId, + status = create, type = group, name = RoomId, + admins = to_member([lists:nth(1, TestClients)], admin), + members = to_member([lists:nth(2, TestClients)])}), + + #'Room'{status = add, type = group, id = RoomId, admins = [#'Member'{status = admin}], members = Members} = + roster_client:send_receive(ClientId1, 2, + #'Room'{id = RoomId, + 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)))], - [roster_client:stop_client(ClientId) || {_, ClientId, _} <- TestClients], - ok. + [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}], +%% Room2 = get_room_id(APhone, RoomName), + Counter = length(Phones), + roster:purge_room(RoomName), + Feed = #muc{name = Room}, + PCs = [{_, AClientId, _, _} | [{BPhoneId, BClientId, _, _}, + {CPhoneId, _, CM, _}, {DPhoneId, _, _, _}] = _] = + [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 = Aff}, Token} + end || {Phone, Aff} <- Phones], %% create and connect fake users + ClientIds = [ClientId || {_, ClientId, _, _} <- PCs], + roster_client:set_filer(ClientIds, filter), + {_ = [Owner | [BAdmin] = _], Members} = roster:split_members([M || {_, _, M, _} <- PCs]), + #'Room'{status = create, id = RoomID, last_msg = #'Message'{type = [sys]}, + admins = [#'Member'{status = admin, alias = <<"Jared">>}, #'Member'{status = admin, alias = <<"Jared">>}], + members = [#'Member'{status = member}, #'Member'{status = member}], type = group} = + roster_client:send_receive(AClientId, Counter, #'Room'{status = create, type = group, %% create room with 4 members + id = RoomName, name = RoomName, admins = [Owner#'Member'{alias = <<"Jared">>}, + BAdmin#'Member'{alias = <<"Jared">>}], members = Members}), + #'Room'{unread = 1} = roster:room(roster:roster_id(CPhoneId), RoomID), +%% Add messages + roster_client:send_receive(BClientId, Counter, + #'Message'{feed_id = #muc{name = Room}, from = BPhoneId, to = Room, + files = [#'Desc'{id = <<"90909">>, payload = <<"Some Text">>}], status = []}), + roster_client:send_receive(BClientId, Counter, + #'Message'{feed_id = #muc{name = Room}, from = BPhoneId, to = Room, + files = [#'Desc'{id = <<"90910">>, payload = <<"Some Text 2">>}], status = []}), + + #'Room'{unread = 2} = roster:room(roster:roster_id(APhone), RoomID), + #'Room'{unread = 3} = roster:room(roster:roster_id(CPhoneId), RoomID), + + #'Room'{status = add, id = RoomID} + = roster_client:send_receive(AClientId, Counter, #'Room'{status = add, id = RoomID, admins = [CM#'Member'{status = admin}]}), + + 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}, + #'Room'{status = add, unread = U} = + roster_client:send_receive(AClientId, Counter + 1, #'Room'{status = add, id = RoomID, admins = [EM]}), + true = is_integer(U), +%% Get the room link by RoomId + #ok{code = #'Room'{links = [#'Link'{id = LinkId}]}} = kvs:get('Room', Room), +%% Assert linkId by Link get request + #ok{code = #'Room'{links = [#'Link'{id = LinkId}], readers = [_ | _]}} = +roster_client:send_receive(AClientId, 1, #'Link'{type = group, status = get, id = LinkId}), +%% Update the link from the administrator and compare it with the old one. + % #'Room'{links = LinkId} =/= + X = roster_client:send_receive(AClientId, 1, #'Link'{id = LinkId, name = Room, type = group, status = update}), +io:format("~nXXXXXXXXXXXXXXXXXXXXX~p~n",[X]), + +%%add member with message size + roster:purge_user(FPhone = <<"6789000">>), + {FClientId, FToken} = roster_client:reg_fake_user(FPhone), + roster_client:start_cli_receive(FClientId, FToken), + FM = #'Member'{presence = online, feed_id = Feed, phone_id = FPhoneId = roster:phone_id(FPhone), status = member}, + SIZE = 2, + #'Room'{status = add, last_msg = #'Message'{id = MesID}} = roster_client:send_receive(AClientId, Counter + 2, + #'Room'{status = add, id = RoomID, admins = [FM], readers = [SIZE]}), + #'History'{data = Msgs} = roster_client:send_receive(FClientId, + #'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, + files = [#'Desc'{id = <<"9as120">>, payload = <<"Some Text From F user">>}], status = []}), + #'Room'{readers = [_ | _]} = roster_client:send_receive(FClientId, #'Room'{id = RoomID, status = get}), + + #'Room'{status = leave, members = [], admins = [#'Member'{feed_id = Feed, phone_id = BPhoneId, status = admin}]} = + roster_client:send_receive(BClientId, 1, #'Room'{status = leave, id = Room}), + + [roster_client:stop_client(element(2, Ph)) || Ph <- PCs], + roster_client:stop_client(FClientId), roster_client:stop_client(EClientId), + ok. + %% = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = + +reg_by_phone(Phones, #'muc'{} = Feed) -> + [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 = Aff}, Token} + end || {Phone, Aff} <- Phones]. + + +test_joinlink() -> + RoomName = RoomId = <<"test_room_joinlink">>, + roster:purge_room(RoomId), + Feed = #muc{name = RoomId}, + + GroupAMembers = [{_APhone = <<"9000000001">>, admin}, + {_BPhone = <<"9000000002">>, member}], + GroupACounter = length(GroupAMembers), + [{APhoneId, AClientId, _, _}, {BPhoneId, BClientId, _, _}] = + GroupAPCs = reg_by_phone(GroupAMembers, Feed), + GroupAClientIds = [ClientId || {_, ClientId, _, _} <- GroupAPCs], + roster_client:set_filer(GroupAClientIds, filter), + {[Owner | _], Members} = roster:split_members([M || {_, _, M, _} <- GroupAPCs]), + RoomInit = #'Room'{status = create, type = group, id = RoomId, name = RoomName, + admins = [Owner#'Member'{alias = <<"Radostin">>}], members = [hd(Members)]}, + #'Room'{id = RoomId, links = [#'Link'{id = LinkId}], + admins = [_RoomOwner = #'Member'{phone_id = APhoneId}], + members = [#'Member'{phone_id = BPhoneId}]} = + roster_client:send_receive(AClientId, GroupACounter, RoomInit), + +%% Test: get room info with fake room link + roster_link:io_error_code(room_not_found) == roster_client:send_receive(BClientId, 1, #'Link'{id = ?BLANK_LINK, type = group, status = get}), +%% Test: get room info with correct room link + kvs:get('Room', RoomId) == #ok{code = roster_client:send_receive(BClientId, 1, #'Link'{id = LinkId, type = group, status = get})}, + GroupBMembers = [{<<"9000000003">>, member}, {<<"9000000004">>, member}], +% GroupBCounter = length(GroupBMembers), + [{CPhoneId, CClientId, _, _}, {_, DClientId, _, _}] = + _GroupBPCs = reg_by_phone(GroupBMembers, Feed), + +%% Test: join room + #'Room'{members = Room2Members} = roster_client:send_receive(CClientId, 1, #'Link'{id = LinkId, type = group, status = join}), + true == lists:keymember(CPhoneId, #'Member'.phone_id, Room2Members), +%% Test: update old link and try to join with new one + % #'Link'{name = LinkId1} = +roster_client:send_receive(AClientId, 1, #'Link'{id = LinkId, name = RoomId, type = group, status = update}). +% , +% roster_client:send_receive(DClientId, 1, #'Link'{id = LinkId1, type = group, status = join}), +% %%% Test: Delete joinlink +% %% Non admin user +% roster_link:io_error_code(invalid_data) == roster_client:send_receive(DClientId, 1, #'Link'{id = LinkId, name = RoomId, type = group, status = delete}), +% %% Admin user, unavailable link (old) +% roster_link:io_error_code(invalid_data) == +% roster_client:send_receive(AClientId, 1, #'Link'{id = LinkId, name = RoomId, type = group, status = delete}), +% %% Test join: unexisting link +% [{_, EClientId, _, _}] = reg_by_phone([{<<"9000000005">>, member}], #muc{}), +% roster_link:io_error_code(room_not_found) == roster_client:send_receive(EClientId, 1, #'Link'{id = LinkId1, type = group, status = join}), +% %% Disconnect users +% [roster_client:stop_client(CId) || CId <- GroupAClientIds ++ [CClientId, DClientId, EClientId]], +% ok. + suite() -> - [ - check_alias_increment(), - add_many_members() - ]. - -%% TODO unify check function, not just a copypaste -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. \ No newline at end of file + [ + check_alias_increment(), + add_many_members() + ]. + +check() -> roster:check(?MODULE). \ No newline at end of file diff --git a/apps/roster/src/test/roster_test.erl b/apps/roster/src/test/roster_test.erl index f8dae451b6f07b168fa14a7ff16ee8c5d67acec5..018c30ccab6737887d294d778f5d5e752e6e04f5 100644 --- a/apps/roster/src/test/roster_test.erl +++ b/apps/roster/src/test/roster_test.erl @@ -47,7 +47,7 @@ test_friend_multi() -> kvs:get('Roster', roster:roster_id(RId)) || {RId, Status} <- [{From, ban}, {To, banned}]], #io{code = #error{code = permission_denied}} = - roster_client:send_receive(BClientId, #'Message'{feed_id = Feed, from = From, to = To, + roster_client:send_receive(AClientId, #'Message'{feed_id = Feed, from = From, to = To, files = [#'Desc'{id = <<"107">>, payload = <<"Ban test!">>}]}), roster_client:send_receive(BClientId, last_res), roster_client:send_receive(AClientId, last_res), @@ -108,19 +108,48 @@ test_roster() -> roster_client:start_cli_receive(ClientId, Token), [Roster] = roster_client:rosters(ClientId, Phone), {Roster, ClientId, Token} end || Phone <- Phones], - #io{code = #error{code = invalid_data}} - = roster_client:send_receive(AClientId, 1, #'Roster'{phone = PhoneA, status = fake_status}), - Wallet = <<"abcd123456789">>, - #'Profile'{status = patch, services = [#'Service'{type = wallet, login = Wallet}]} + timer:sleep(1000), + #errors{code = [<<"666">>]} = + roster_client:send_receive(AClientId, 1, #'Roster'{phone = PhoneA, status = fake_status}), +%% #io{code = #error{code = invalid_data}} +%% = roster_client:send_receive(AClientId, 1, #'Roster'{phone = PhoneA, status = fake_status}), +%% Wallet = <<"abcd123456789">>, +%% #'Profile'{status = patch, services = [#'Service'{type = wallet, login = Wallet}]} +%% = roster_client:send_receive(AClientId, 1, #'Profile'{status = patch, phone = PhoneA, +%% services = [#'Service'{login = Wallet, type = wallet}]}), +%% Wallet2 = <<"abcd1234">>, +%% #'Profile'{status = patch, rosters = [], services = [#'Service'{type = wallet, login = Wallet2}]} +%% = roster_client:send_receive(AClientId, 1, #'Profile'{status = patch, phone = PhoneA, +%% services = [#'Service'{login = Wallet2, type = wallet}]}), +%% {ok, #'Profile'{status = [], services = [#'Service'{login = Wallet2}]}} +%% = kvs:get('Profile', PhoneA), + + Wallet = <<"0x9d3cba69e0150a21437df4ad0949eb549f1c073d">>, + WalletSrc = + #'Service'{id = <<"userID_wallet_timestamp">>, type = wallet, + login = <<"Name">>, data = Wallet}, + WalletSrcResp = WalletSrc#'Service'{status = []}, + #'Profile'{status = patch, services = [WalletSrcResp]} = roster_client:send_receive(AClientId, 1, #'Profile'{status = patch, phone = PhoneA, - services = [#'Service'{login = Wallet, type = wallet}]}), - Wallet2 = <<"abcd1234">>, - #'Profile'{status = patch, rosters = [], services = [#'Service'{type = wallet, login = Wallet2}]} + services = [WalletSrc#'Service'{status = add}]}), + Wallet2 = <<"0xeeeeba69e0150a21437df4ad0949eb549f1eeeed">>, + {ok, #'Profile'{status = [], services = [#'Service'{data = Wallet}]}} + = kvs:get('Profile', PhoneA), + + WalletSrc2 = WalletSrc#'Service'{data = Wallet2}, + WalletSrcResp2 = WalletSrc2#'Service'{status = []}, + #'Profile'{status = patch, rosters = [], services = [WalletSrcResp2]} = roster_client:send_receive(AClientId, 1, #'Profile'{status = patch, phone = PhoneA, - services = [#'Service'{login = Wallet2, type = wallet}]}), - {ok, #'Profile'{status = [], services = [#'Service'{login = Wallet2}]}} + services = [WalletSrc2#'Service'{status = add}]}), + + {ok, #'Profile'{status = [], services = [#'Service'{data = Wallet2}]}} = kvs:get('Profile', PhoneA), + #'Profile'{status = patch, rosters = [], services = []} + = roster_client:send_receive(AClientId, 1, #'Profile'{status = patch, phone = PhoneA, + services = [WalletSrc2#'Service'{status = remove}]}), + {ok, #'Profile'{status = [], services = []}} = kvs:get('Profile', PhoneA), + Feed = roster:feed_key(p2p, APhoneId, BPhoneId), roster_client:set_filer([AClientId, BClientId], filter_friend), [_, FromRId] = roster:parts_phone_id(APhoneId), @@ -130,7 +159,7 @@ test_roster() -> #'Contact'{phone_id = APhoneId, created = FromCreated} = roster:get_contact(roster:roster_id(BPhoneId), APhoneId), true = ToCreated /= 0, true = FromCreated /= 0, roster_client:send_receive(BClientId, Counter + 1, #'Friend'{phone_id = BPhoneId, friend_id = APhoneId, status = confirm}), - Settings = [#'Feature'{id = <<"1">>, key = <<"mute">>, value = <<"1">>}], + Settings = [#'Feature'{id = <<"1">>, key = <<"mute">>, value = <<"1">>, group = ?DUMMY_GROUP}], #'Roster'{userlist = [#'Contact'{reader = [RMId, RMId]}, #'Contact'{reader = [0, 0]}]} = roster:roster(roster:roster_id(APhoneId)), true = is_integer(RMId), @@ -139,8 +168,7 @@ test_roster() -> Settings = [roster:get_data(<<"mute">>, Settings2)], roster_client:set_filer([AClientId, BClientId], filter), roster_client:stop_client(AClientId), - timer:sleep(1000), - + % timer:sleep(1000), roster_client:start_cli_receive(AClientId, AToken), roster_client:set_filer([AClientId, BClientId], filter_friend), #'Contact'{phone_id = APhoneId, settings = Settings2, created = Created, update = Update} = @@ -155,16 +183,22 @@ test_roster() -> roster_client:send_receive(BClientId, 1, #'Friend'{status = update, phone_id = BPhoneId, friend_id = APhoneId, settings = Settings3}), roster_client:set_filer([AClientId, BClientId], filter), [_, _, _] = Settings4, + + AFeed = #p2p{from = APhoneId, to = APhoneId}, + roster_client:send_receive(AClientId, last_res), %% self chat test + #'Message'{status = []} = roster_client:send_receive(AClientId, 1, #'Message'{status = [], feed_id = AFeed, from = APhoneId, to = APhoneId, + files = [#'Desc'{id = <<"A7">>, payload = <<"SelfTestA!">>}]}), + [#'Message'{status = []}, #'Message'{status = clear}] = roster_client:send_receive(AClientId, unsort_last_res), + timer:sleep(500), roster_client:stop_vnodes(), - roster_client:send(AClientId, #'Message'{feed_id = Feed, from = APhoneId, to = BPhoneId, files = [#'Desc'{id = <<"1">>, payload = <<"Opa 77777777777777">>}, %list_to_binary([<<"777">> | [integer_to_binary(rand:uniform(99999999)) || _<-lists:seq(1,80000)]]) #'Desc'{id = <<"3">>, payload = <<"First TestA!">>}], status = []}), roster_client:send(BClientId, #'Message'{feed_id = Feed, from = BPhoneId, to = APhoneId, files = [#'Desc'{id = <<"7">>, payload = <<"TestB!">>}], status = []}), roster_client:start_vnodes(), roster_client:receive_drop(), - #'History'{data = [#'Message'{id = IdB}, #'Message'{id = Id}, _]} + #'History'{data = [#'Message'{id = IdB}, #'Message'{id = Id},_]} = roster_client:send_receive(AClientId, #'History'{feed = Feed, roster_id = APhoneId, size = [], entity_id = 0, status = get}), roster_client:send_receive(AClientId, last_res), roster_client:send_receive(AClientId, 3, #'History'{feed = Feed, roster_id = APhoneId, entity_id = 0, status = update}), @@ -175,7 +209,7 @@ test_roster() -> #'Message'{id = IdAA} = roster_client:send_receive(AClientId, 2, #'Message'{feed_id = Feed, from = APhoneId, to = APhoneId, files = [#'Desc'{id = <<"1">>, payload = <<"Test AA!">>}, #'Desc'{id = <<"3">>, payload = <<"Myself">>}], status = []}), - #io{code = #error{code = invalid_data}} = %% invalid message type + #errors{code = [<<"666">>]} = %% invalid message type roster_client:send_receive(AClientId, #'Message'{status = [], type = [<<"reply">>], feed_id = Feed, from = APhoneId, to = BPhoneId, files = [#'Desc'{id = <<"3">>, payload = <<"invalid type message!!">>}]}), @@ -192,51 +226,134 @@ test_roster() -> roster_client:receive_last(AClientId, 3, #'History'{feed = Feed, roster_id = APhoneId, entity_id = IdA, status = update}), #'Message'{status = delete} = - roster_client:send_receive(AClientId, 2, #'Message'{id = IdA, from = APhoneId, feed_id = Feed, seenby = [-1], status = delete}), + roster_client:send_receive(AClientId, 2, #'Message'{id = IdA, from = APhoneId, to = BPhoneId, feed_id = Feed, seenby = [-1], status = delete}), + + #'History'{status = get, data = [#'Message'{id = IdA}, _]} = + roster_client:send_receive(AClientId, #'History'{feed = Feed, roster_id = APhoneId, entity_id = IdA, size = -1, status = get}), + + #'History'{status = double_get, data = [#'Message'{id = IdA}, _]} = + roster_client:send_receive(AClientId, #'History'{feed = Feed, roster_id = APhoneId, entity_id = IdA, size = 1, status = double_get}), + + #'History'{status = get, data = [#'Message'{id = IdA}]} = + roster_client:send_receive(AClientId, #'History'{feed = Feed, roster_id = APhoneId, entity_id = IdA, size = 1, status = get}), + + #'Message'{} = roster_client:send_receive(AClientId, Counter, #'Message'{feed_id = Feed, from = APhoneId, to = BPhoneId, + files = [#'Desc'{id = <<"33">>, payload = <<"After delete msg!">>}], status = []}), + + #'History'{status = double_get, data = [_,#'Message'{id = IdA}, _]} = + roster_client:send_receive(AClientId, #'History'{feed = Feed, roster_id = APhoneId, entity_id = IdA, size = 1, status = double_get}), + #io{} = - roster_client:send_receive(AClientId, #'Message'{id = IdA, from = APhoneId, feed_id = Feed, seenby = [-1], status = delete}), + roster_client:send_receive(AClientId, #'Message'{id = IdA, from = APhoneId, to = BPhoneId, feed_id = Feed, seenby = [-1], status = delete}), #'Message'{status = edit} = roster_client:send_receive(AClientId, 2, #'Message'{id = Id, feed_id = Feed, from = APhoneId, to = BPhoneId, files = [#'Desc'{id = <<"7">>, payload = <<"Edite TestA!">>}], status = edit}), #'Message'{status = clear} = - roster_client:send_receive(AClientId, 1, #'History'{feed = Feed, roster_id = APhoneId, status = delete}), + roster_client:send_receive(AClientId, 2, #'History'{feed = Feed, roster_id = APhoneId, status = delete}), + + #'Contact'{last_msg = #'Message'{id = ClearId, next = []}} + = roster:user(roster:roster_id(APhoneId), BPhoneId), + + {ok, #'Message'{next = N}} = kvs:get('Message', ClearId), + true = N /= [], + + #'History'{status = get, data = [#'Message'{id = ClearId, next = []}]} = + roster_client:send_receive(AClientId, #'History'{feed = Feed, roster_id = APhoneId, entity_id = ClearId, size = 1, status = get}), + roster_client:stop_client(AClientId), roster_client:start_cli_receive(AClientId, AToken), #'Profile'{status = get} = roster_client:send_receive(AClientId, #'Profile'{status = get}), #'Message'{id = IdLast} = roster_client:send_receive(BClientId, 2, #'Message'{feed_id = Feed, from = BPhoneId, to = APhoneId, files = [#'Desc'{id = <<"7">>, payload = <<"TestB2222!">>}], status = []}), + + #'History'{status = get, data = [_, #'Message'{status = clear}|_]} = %% if msg_id < clear_id then clear message doesn't stop history + roster_client:send_receive(AClientId, #'History'{feed = Feed, roster_id = APhoneId, entity_id = IdA, size = 5, status = get}), + #'Message'{id = IdEd, status = edit} = roster_client:send_receive(BClientId, 2, #'Message'{id = IdLast, feed_id = Feed, from = BPhoneId, to = APhoneId, files = [#'Desc'{id = <<"7">>, payload = <<"Edit TestB!">>}], status = edit}), #'History'{} = roster_client:send_receive(AClientId, #'History'{feed = Feed, roster_id = APhoneId, size = -30, entity_id = IdEd, status = get}), [#'History'{}, _, #'Profile'{rosters = [#'Roster'{userlist = [#'Contact'{reader = LastId}, #'Contact'{phone_id = APhoneId}]}]}] = 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!">>}], status = []}), + 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!">>}], status = []}), - - #'History'{data = [#'Message'{files = [#'Desc'{mime = <<"image">>}]}, #'Message'{files = [#'Desc'{mime = <<"image">>}]}]} = + files = [#'Desc'{id = <<"711">>, mime = <<"image">>, payload = <<"Image mime test2!">>}, #'Desc'{id = <<"712">>, mime = <<"thumb">>, payload = <<"Image mime test2!">>, parentid = <<"711">>}], status = []}), + +%% #'History'{status = image, data = [#'Message'{files = [#'Desc'{mime = <<"image">>}]}, #'Message'{files = [#'Desc'{mime = <<"image">>}]}]} = +%% roster_client:send_receive(AClientId, #'History'{status = image, feed = [], roster_id = APhoneId, size = -5, entity_id = ImageId, +%% data = [#'Message'{feed_id = Feed, files = [#'Desc'{mime = <<"image">>}]}]}), +%% +%% #'History'{data = [#'Message'{id = ImageId, files = [#'Desc'{mime = <<"image">>}]}]} = +%% roster_client:send_receive(AClientId, #'History'{status = image, feed = [], roster_id = APhoneId, size = 5, entity_id = ImageId, +%% data = [#'Message'{feed_id = Feed, files = [#'Desc'{mime = <<"image">>}]}]}), +%% +%% #'History'{data = [#'Message'{files = [#'Desc'{mime = <<"image">>}]}, #'Message'{files = [#'Desc'{mime = <<"image">>}]}]} = +%% roster_client:send_receive(AClientId, #'History'{status = image, feed = [], roster_id = APhoneId, size = -5, entity_id = [], + + #errors{code = [<<"666">>]} + = roster_client:send_receive(BClientId, #'Message'{feed_id = Feed, from = BPhoneId, to = APhoneId, + files = [#'Desc'{id = <<"111">>, mime = <<"text">>, payload = <<"sys type!">>}], status = [sys]}), + +%% test invalid descs + #errors{code = [<<"666">>]} + = roster_client:send_receive(BClientId, #'Message'{feed_id = Feed, from = BPhoneId, to = APhoneId, + files = [#'Desc'{id = <<>>, mime = <<"text">>, payload = <<"empty desc id!">>}], status = []}), + + #errors{code = [<<"666">>]} + = roster_client:send_receive(BClientId, #'Message'{feed_id = Feed, from = BPhoneId, to = APhoneId, + files = [#'Desc'{id = <<"7111">>, mime = <<"image">>, payload = <<"Image mime test1!">>}, #'Desc'{id = <<"712">>, mime = <<"thumb">>, + payload = <<"Image mime test2!">>}], status = fake}), + #errors{code = [<<"666">>]} + = roster_client:send_receive(BClientId, #'Message'{feed_id = Feed, from = BPhoneId, to = APhoneId, + files = [#'Desc'{id = <<"7111">>, mime = <<"image">>, + payload = <<"Image mime test2!">>}, #'Desc'{id = <<"712">>, mime = <<"thumb">>, payload = <<"Image mime test2!">>}], status = []}), + + #errors{code = [<<"666">>]} + = roster_client:send_receive(BClientId, #'Message'{feed_id = Feed, from = BPhoneId, to = APhoneId, + files = [#'Desc'{id = <<"71111">>, mime = <<"image">>, payload = <<"Image mime test3!">>}], status = []}), +%% #io{code = #error{code = invalid_data}} +%% = roster_client:send_receive(BClientId, #'Message'{feed_id = Feed, from = BPhoneId, to = APhoneId, +%% files = [#'Desc'{id = <<"7111">>, mime = <<"image">>, +%% payload = <<"Image mime test2!">>}, #'Desc'{id = <<"712">>, mime = <<"thumb">>, payload = <<"Image mime test2!">>}], status = []}), +%% +%% #io{code = #error{code = invalid_data}} +%% = roster_client:send_receive(BClientId, #'Message'{feed_id = Feed, from = BPhoneId, to = APhoneId, +%% files = [#'Desc'{id = <<"71111">>, mime = <<"image">>, payload = <<"Image mime test3!">>}], status = []}), + + + #'History'{data = [#'Message'{files = [#'Desc'{mime = <<"image">>}, #'Desc'{mime = <<"thumb">>}]}, #'Message'{files = [#'Desc'{mime = <<"image">>}, #'Desc'{mime = <<"thumb">>}]}]} = roster_client:send_receive(AClientId, #'History'{status = get, feed = [], roster_id = APhoneId, size = -5, entity_id = ImageId, data = [#'Message'{feed_id = Feed, files = [#'Desc'{mime = <<"image">>}]}]}), - #'History'{data = [#'Message'{id = ImageId, files = [#'Desc'{mime = <<"image">>}]}]} = + #'History'{data = [#'Message'{id = ImageId, files = [#'Desc'{mime = <<"image">>}, #'Desc'{mime = <<"thumb">>}]}]} = roster_client:send_receive(AClientId, #'History'{status = get, feed = [], roster_id = APhoneId, size = 5, entity_id = ImageId, data = [#'Message'{feed_id = Feed, files = [#'Desc'{mime = <<"image">>}]}]}), - #'History'{data = [#'Message'{files = [#'Desc'{mime = <<"image">>}]}, #'Message'{files = [#'Desc'{mime = <<"image">>}]}]} = + #'History'{data = [#'Message'{files = [#'Desc'{mime = <<"image">>}, #'Desc'{mime = <<"thumb">>}]}, #'Message'{files = [#'Desc'{mime = <<"image">>}, #'Desc'{mime = <<"thumb">>}]}]} = roster_client:send_receive(AClientId, #'History'{status = get, feed = [], roster_id = APhoneId, size = -5, entity_id = [], data = [#'Message'{feed_id = Feed, files = [#'Desc'{mime = <<"image">>}]}]}), - #'History'{data = [#'Message'{files = [#'Desc'{mime = <<"text">>}]}, #'Message'{files = [#'Desc'{mime = <<"text">>}]}]} - = roster_client:send_receive(AClientId, #'History'{status = get, feed = [], roster_id = APhoneId, size = -3, entity_id = ImageId, + #'History'{data = [#'Message'{files = [#'Desc'{mime = <<"text">>}]}, + #'Message'{files = [#'Desc'{mime = <<"text">>}]}]} + = roster_client:send_receive(AClientId, #'History'{status = text, feed = [], roster_id = APhoneId, size = -4, entity_id = ImageId, data = [#'Message'{feed_id = Feed, files = [#'Desc'{mime = <<"text">>}]}]}), #'History'{data = [#'Message'{}, #'Message'{}]} - = roster_client:send_receive(AClientId, #'History'{status = get, feed = [], roster_id = APhoneId, size = [], entity_id = ImageId, + = roster_client:send_receive(AClientId, #'History'{status = image, feed = [], roster_id = APhoneId, size = [], entity_id = ImageId, data = [#'Message'{id = IdLast, feed_id = Feed, files = []}]}), #'History'{data = [#'Message'{}, #'Message'{}]} = roster_client:send_receive(AClientId, #'History'{status = get, feed = [], roster_id = APhoneId, size = [], entity_id = IdLast, data = [#'Message'{id = ImageId, feed_id = Feed, files = []}]}), + #'Message'{id = LinkId} = roster_client:send_receive(BClientId, 2, #'Message'{feed_id = Feed, from = BPhoneId, to = APhoneId, + files = [#'Desc'{id = <<"715">>, mime = <<"link">>, payload = <<"link mime test!">>}], status = []}), + #'History'{data = [#'Message'{id = LinkId}]} + = roster_client:send_receive(AClientId, #'History'{status = get, feed = [], roster_id = APhoneId, size = -30, entity_id = [], + data = [#'Message'{feed_id = Feed, files = [#'Desc'{mime = <<"link">>}]}]}), + + #'History'{data = [#'Message'{id = LinkId}]} + = roster_client:send_receive(AClientId, #'History'{status = link, feed = [], roster_id = APhoneId, size = -30, entity_id = [], + data = [#'Message'{feed_id = Feed, files = []}]}), + #'History'{data = StickerPack} = roster_client:send_receive(AClientId, #'History'{feed = #'StickerPack'{}, roster_id = APhoneId, size = 1, status = get}), StickerPack = [S || #'StickerPack'{} = S <- StickerPack], @@ -278,13 +395,14 @@ test_huge_messages(N) -> test_reg(Phone) when ?HOST == ?LOC -> kvs:put(#'Whitelist'{phone = Phone, created = roster:now_msec()}), roster:purge_user(Phone), + timer:sleep(1000), DevKey = iolist_to_binary([<<"DevKey_">>, Phone]), RegClientId = roster_client:gen_name_reg(Phone, DevKey), roster:ttl(), application:set_env(roster, auth_ttl, 1), roster_client:start_client(RegClientId), #io{code = #ok{code = sms_sent}} = roster_client:send_receive(RegClientId, #'Auth'{phone = Phone, type = reg, services = [], dev_key = DevKey}), - Settings = [#'Feature'{key = ?DEFAULT_LANGUAGE_KEY, value = <<"English:en">>, group = ?LANG_KEY}], + Settings = [#'Feature'{id = ?DEFAULT_LANGUAGE_KEY, key = ?DEFAULT_LANGUAGE_KEY, value = <<"English:en">>, group = ?LANG_KEY}], #io{code = #error{code = mismatch_user_data}} = roster_client:send_receive(RegClientId, #'Auth'{phone = Phone, type = verify, dev_key = <<"1">>, sms_code = <<"133">>, settings = Settings}), [begin @@ -297,7 +415,7 @@ test_reg(Phone) when ?HOST == ?LOC -> #io{code = #ok2{src = {ClientId, _}}} = roster_client:send_receive(RegClientId, #'Auth'{phone = Phone, type = verify, dev_key = DevKey, sms_code = Sms, settings = Settings}), {ok, #'Auth'{last_online = LO, type = verified}} = kvs:get('Auth', ClientId), - LO /= [], + true = LO /= [], roster_client:stop_client(RegClientId), roster_client:start_client(RegClientId), #io{code = #ok{code = sms_sent}} = roster_client:send_receive(RegClientId, #'Auth'{phone = Phone, type = reg, services = [], dev_key = DevKey}), @@ -391,7 +509,7 @@ test_reg_jwt() when ?HOST == ?LOC -> roster_client:start_client(ClientIdReg), #io{code = #ok2{code = jwt_sent}} = roster_client:send_receive(ClientIdReg, #'Auth'{phone = Phone, type = reg, services = [jwt], dev_key = DevKey}), - Settings = [#'Feature'{key = ?DEFAULT_LANGUAGE_KEY, value = <<"English:en">>, group = ?LANG_KEY}], + Settings = [#'Feature'{id = ?DEFAULT_LANGUAGE_KEY, key = ?DEFAULT_LANGUAGE_KEY, value = <<"English:en">>, group = ?LANG_KEY}], {ok, #'Auth'{sms_code = Sms}} = kvs:get('Auth', ClientIdReg), #io{code = #ok2{src = {ClientId, Token}}} = roster_client:send_receive(ClientIdReg, @@ -404,30 +522,34 @@ test_reg_jwt() when ?HOST == ?LOC -> roster_client:stop_client(ClientId); test_reg_jwt() -> roster:info("test_reg_jwt() skipped", []), ok. -test_latency() -> test_latency(?HOST, 10). -test_latency(Server, MsgNumber) -> +test_latency() -> test_latency(?HOST, [], 10). +%% Remote test with ssl: test_latency(Server, [ssl,{port,8883}], MsgNumber) +test_latency(Server, Opts, MsgNumber) -> Phones = [_PhoneA, _] = [<<"71077">>, <<"72077">>], Counter = length(Phones), - [{From, AClientId, _}, {To, BClientId, _}] = [begin - roster:purge_user(Phone), - {ClientId, Token} = roster_client:reg_fake_user(Phone, <<"DevKey_", Phone/binary>>, 1000, [{host, Server}]), - roster_client:start_cli_receive(ClientId, Token, 0, [{host, Server}]), - [Roster] = roster_client:rosters(ClientId, Phone), {Roster, ClientId, Token} - end || Phone <- Phones], - roster_client:set_filer([AClientId, BClientId], filter_friend), - timer:sleep(1000), - roster_client:send_receive(AClientId, Counter, #'Friend'{phone_id = From, friend_id = To, status = request}), - roster_client:send_receive(BClientId, Counter + 1, #'Friend'{phone_id = To, friend_id = From, status = confirm}), -% , roster_client:receive_drop(), - roster_client:set_filer([AClientId, BClientId], filter), + [{FromRoster = #'Roster'{id = FromId, userlist = [_|Fs]}, AClientId, _}, {ToRoster = #'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]), + {Roster, ClientId, Token} + end || Phone <- Phones], + [From, To]= [roster:phone_id(Phone, Id)||{Phone, Id} <-lists:zip(Phones, [FromId, ToId])], + case Fs of + [] ->roster_client:set_filer([AClientId, BClientId], filter_friend), + catch roster_client:send_receive(AClientId, Counter, #'Friend'{phone_id = From, friend_id = To, status = request}), + catch roster_client:send_receive(BClientId, Counter + 1, #'Friend'{phone_id = To, friend_id = From, status = confirm}), + roster_client:set_filer([AClientId, BClientId], filter); + _ -> ok + end, Feed = roster:feed_key(p2p, From, To), {Mx, Mn, Average} = lists:foldl(fun(_, {Max, Min, Av}) -> T = erlang:timestamp(), - roster_client:send(AClientId, #'Message'{feed_id = Feed, from = From, to = To, + 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(BClientId, #'Message'{feed_id = Feed, from = To, to = From, + 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} @@ -453,7 +575,7 @@ test_update_contacts() -> [roster_client:send_receive(roster_client:gen_name(roster:phone(To)), 3, #'Friend'{phone_id = To, friend_id = From, status = confirm}) || To <- [B, C]], roster_client:set_filer([AClientId, BClientId, CClientId], filter), %% roster_client:receive_drop(), - [#'Roster'{status = patch}] = roster_client:receive_last(AClientId, 1, #'Roster'{id = roster:roster_id(From), names = "Patch", status = patch}), + [#'Roster'{status = patch}] = roster_client:receive_last(AClientId, 1, #'Roster'{id = roster:roster_id(From), names = <<"Patch">>, status = patch}), %roster_client:send_receive(AClientId,#'Roster'{id=FromId, userlist=PhoneIds, status= del}), [BPhone, _] = roster:parts_phone_id(B), roster_client:send_receive(BClientId, last_res), roster_client:send_receive(AClientId, last_res), @@ -475,7 +597,7 @@ misc_test_roster(Phone) -> #io{code = #ok2{src = {ClientId, Token}}} = roster_client:send_receive(RegClientId, #'Auth'{phone = {fake, Phone}, - settings = [#'Feature'{key = ?DEFAULT_LANGUAGE_KEY, value = <<"English:en">>, group = ?LANG_KEY}], + settings = [#'Feature'{id = ?DEFAULT_LANGUAGE_KEY, key = ?DEFAULT_LANGUAGE_KEY, value = <<"English:en">>, group = ?LANG_KEY}], type = reg, dev_key = DevKey, sms_code = <<"712">>}), roster_client:stop_client(RegClientId), timer:sleep(1000), @@ -487,7 +609,7 @@ misc_test_roster(Phone) -> roster_client:receive_drop(), {io, {ok, _Ref}, #'Roster'{}} = roster_client:send_receive(ClientId, #'Search'{id = RosterId, ref = <<"phone">>, field = <<"phone">>, type = '==', value = [<<"3738773">>, <<"77">>], status = contact}), - #'Roster'{} = roster_client:send_receive(ClientId, #'Roster'{id = RosterId, names = "Patch", status = patch}), + #'Roster'{} = roster_client:send_receive(ClientId, #'Roster'{id = RosterId, names = <<"Patch">>, status = patch}), roster_client:stop_client(ClientId). get_room_id(Phone, RoomName) -> @@ -518,10 +640,11 @@ test_multi_muc() -> roster_client:stop_client(roster_client:gen_name_reg(APhone, <<"DevKey_001A">>)), roster_client:start_cli_receive(AClientId2, AToken2), [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}) + name = Room, admins = [M#'Member'{feed_id = #muc{name = Room}} || #'Member'{} = M <-[Owner#'Member'{alias = <<"Rick">>} | TAdmins]], + members = [M#'Member'{feed_id = #muc{name = Room}} || #'Member'{} = M <-Members]}) || Room <- RoomNames], - [roster_client:send_receive(AClientId, Counter+1, #'Message'{feed_id = #muc{name = Room}, from = APhoneId, + [roster_client:send_receive(AClientId, Counter+1, #'Message'{feed_id = #muc{name = Room}, from = APhoneId, to = Room, files = [#'Desc'{id = <<"fake_id2">>, payload = <<"Reverse Message in ", Room/binary, " from ", AClientId/binary>>}], status = []}) || Room <- lists:reverse(RoomNames)], @@ -546,7 +669,7 @@ test_muc() -> {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, phone_id = PhoneId, status = Aff}, Token} + {PhoneId, ClientId, #'Member'{feed_id = #muc{name = Room}, presence = online, phone_id = PhoneId, status = Aff}, Token} end || {Phone, Aff} <- Phones], %% create and connect fake users ClientIds = [ClientId || {_, ClientId, _, _} <- PCs], roster_client:set_filer(ClientIds, filter), @@ -569,13 +692,13 @@ test_muc() -> id = [], name = RoomName, admins = [Owner | TAdmins], members = [Owner | Members]}), [#io{code = #error{code = invalid_data}} = roster_client:send_receive(AClientId, #'Room'{status = create, type = group, %% duplicate owner - id = [], name = InvalidName, admins = [Owner | TAdmins], members = [Members]}) + id = [], name = InvalidName, admins = [Owner | TAdmins], members = Members}) || InvalidName <- [<<"s">>, <<"лонглонглонглонглонглонглонглонглонглонг">>]], roster_client:send_receive(CClientId, last_res), DAlias = <>/binary, DSur/binary>>, - [#io{code = #error{code = invalid_data}} = roster_client:send_receive(AClientId, + [#errors{code = [<<"666">>]} = roster_client:send_receive(AClientId, #'Room'{status = create, type = group, id = RoomName, name = RoomName, members = Members, data = Data, admins = [Owner#'Member'{alias = <<"Jared">>}, BAdmin#'Member'{alias = <<"Jared">>}]}) || @@ -684,11 +807,11 @@ test_muc() -> lists:reverse(roster_client:receive_last(AClientId, Counter - 1, #'Room'{status = remove, id = Room, members = [DM]})), #io{code = #error{code = permission_denied}} = roster_client:send_receive(CClientId, 1, - #'Message'{feed_id = #muc{name = Room}, from = CPhoneId, + #'Message'{feed_id = #muc{name = Room}, from = CPhoneId, to = Room, files = [#'Desc'{id = <<"7">>, payload = <<"MUC removed member test!: ", CClientId/binary>>}], status = []}), - #io{code = #error{code = invalid_data}} = roster_client:send_receive(AClientId, 1, - #'Message'{feed_id = #muc{name = Room}, from = APhoneId, type = [sys], + #errors{code = [<<"666">>]} = roster_client:send_receive(AClientId, + #'Message'{feed_id = #muc{name = Room}, from = APhoneId, type = [sys], to = Room, files = [#'Desc'{id = <<"5">>, payload = <<"MUC sys test!: ", AClientId/binary>>}], status = []}), #'Room'{status = get, unread = 3} = roster_client:send_receive(DClientId, #'Room'{status = get, id = Room}), @@ -721,21 +844,21 @@ test_muc() -> FreezeTime = application:get_env(roster, freeze_time, 5 * 60 * 1000), application:set_env(roster, freeze_time, 0), - Settings2 = [#'Feature'{id = <<"1">>, key = <<"dummy">>}], - [#'Message'{}, #'Room'{description = Desc, tos = channel, status = patch, + Settings2 = [#'Feature'{id = <<"1">>, key = <<"dummy">>, group = <<"DUMMY_GROUP">>}], + [#'Message'{}, #'Room'{description = Desc, tos = <<"channel">>, status = patch, tos_update = TosUpdate, settings = Settings2, update = Update}] = roster_client:receive_last(AClientId, Counter - 2, #'Room'{status = patch, id = Room, description = Desc, - data = [#'Desc'{id = <<"av_id">>, mime = <<"iamge">>, payload = <<"aaaa">>}], %% change avatar - tos = channel, settings = Settings2}), + data = [#'Desc'{id = <<"av_id">>, mime = <<"image">>, payload = <<"aaaa">>}], %% change avatar + tos = <<"channel">>, settings = Settings2}), true = TosUpdate > 0, true = Update > 0, application:set_env(roster, freeze_time, FreezeTime), - #io{code = #error{code = incorrect_data}} = + #errors{code = [<<"666">>]} = roster_client:send_receive(AClientId, #'Room'{status = patch, id = Room, settings = [{test_settings, 1}]}), - {ok, #'Room'{description = Desc, tos = channel, status = [], tos_update = TosUpdate, + {ok, #'Room'{description = Desc, tos = <<"channel">>, status = [], tos_update = TosUpdate, settings = Settings2}} = kvs:get('Room', Room), {ok, #'Roster'{roomlist = [#'Room'{id = Room, tos_update = TosUpdate}]}} = kvs:get('Roster', roster:roster_id(APhoneId)), @@ -753,7 +876,7 @@ test_muc() -> Nm = #'Member'{status = patch, alias = <<"Jared">>, reader = 0 , settings = Fts} = roster_client:send_receive(CClientId, Counter - 2, - #'Member'{status = patch, id = CId, alias = <<"Jared">> = AAlias, settings = Fts}), + #'Member'{status = patch, feed_id = #muc{name = Room}, id = CId, alias = <<"Jared">> = AAlias, settings = Fts}), {ok, #'Member'{reader = CReader}} = kvs:get('Member', CId), Nm2 = Nm#'Member'{status = removed, reader = CReader}, {ok, Nm2} = kvs:get('Member', CId), @@ -761,19 +884,19 @@ test_muc() -> #'Member'{id = CId, alias = <<"Jared">>} = roster:muc_member(CPhoneId, Room), #'Member'{status = patch, alias = <<"Jared3">>} = roster_client:send_receive(CClientId, Counter - 2, - #'Member'{status = patch, id = CId, alias = <<"Jared3">>}), + #'Member'{status = patch, feed_id = #muc{name = Room}, id = CId, alias = <<"Jared3">>}), Alias = <<"John">>, #'Member'{id = Id, reader = BReaderId} = roster:muc_member(BPhoneId, Room), #'Member'{status = patch, alias = Alias, reader = 0} = %% verify last read message in member reader field - roster_client:send_receive(BClientId, Counter-2, #'Member'{status = patch, id = Id, alias = Alias}), - {ok, #'Member'{alias = Alias, status = admin}} = kvs:get('Member', Id), + roster_client:send_receive(BClientId, Counter-2, #'Member'{feed_id = #muc{name = Room}, status = patch, id = Id, alias = Alias}), + {ok, #'Member'{feed_id = #muc{name = Room}, alias = Alias, status = admin}} = kvs:get('Member', Id), #reader{pos = 0} = kvs_stream:load_reader(BReaderId), #'Room'{status = get, type = group, unread = 0} = roster_client:send_receive(AClientId, #'Room'{status = get, id = Room}), #'Room'{status = get, type = group, unread = 7} = roster_client:send_receive(BClientId, #'Room'{status = get, id = Room}), roster_client:send_receive(AClientId, Counter - 2, - #'Message'{feed_id = #muc{name = Room}, from = APhoneId, + #'Message'{feed_id = #muc{name = Room}, from = APhoneId, to = Room, files = [#'Desc'{id = <<"7">>, payload = <<"TestA!">>}], status = []}), #'Room'{status = add, members = [#'Member'{}], admins = [#'Member'{}], readers = RoomRdrMsgIds = [_,_]} = roster_client:send_receive(AClientId, Counter - 1, @@ -896,7 +1019,7 @@ test_muc() -> %% get "reply" history with message link as 'Message' model #'History'{data = [#'Message'{id = LinkedId}]} - = roster_client:send_receive(AClientId, 1, #'History'{status = get_reply, entity_id = LastReadId}), + = roster_client:send_receive(AClientId, 1, #'History'{status = get_reply, roster_id = APhoneId, entity_id = LastReadId}), HistLimit = 30, Msgs = [#'Message'{} = roster_client:send_receive(BClientId, Counter, @@ -909,7 +1032,7 @@ test_muc() -> {EClientId, EToken} = roster_client:reg_fake_user(EPhone), roster_client:start_cli_receive(EClientId, EToken), [EPhoneId] = roster_client:rosters(EClientId, EPhone), - EMember = #'Member'{presence = online, phone_id = EPhoneId, status = member}, + EMember = #'Member'{presence = online, feed_id = #muc{name = Room}, phone_id = EPhoneId, status = member}, #'Room'{status = add, members = [#'Member'{id = EMemId, status = member, presence = online}], last_msg = #'Message'{id = LastRoomMsgId}} = roster_client:send_receive(AClientId, Counter+1, #'Room'{status = add, id = Room, members = [EMember], readers = [UserHistLimit]}), @@ -921,7 +1044,7 @@ test_muc() -> #'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'{feed = #muc{name = Room}, roster_id = EPhoneId, entity_id = LastRoomMsgId, size = -30, status = get}), + #'History'{feed = #muc{name = Room}, roster_id = EPhoneId, entity_id = 0, size = 30, status = get}), UserHistLimit = length(History2) - 1, #'Room'{status = get, type = group, unread = LimUnread} = roster_client:send_receive(EClientId, #'Room'{status = get, id = Room}), @@ -953,13 +1076,14 @@ test_muc_msgs() -> %% Room2 = get_room_id(APhone, RoomName), Counter = length(Phones), roster:purge_room(Room), + Feed = #muc{name = Room}, PCs = [{APhoneId, AClientId, _} | [{BPhoneId, BClientId, _}, {CPhoneId, CClientId, _}, {DPhoneId, DClientId, _} | _] = TPCs] = [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), - Member = #'Member'{presence = online, phone_id = PhoneId, status = Aff}, + Member = #'Member'{presence = online, feed_id = Feed, phone_id = PhoneId, status = Aff}, {PhoneId, ClientId, Member} end || {Phone, Aff} <- Phones], _ = [ClientId || {_, ClientId, _, _} <- PCs], @@ -976,16 +1100,25 @@ test_muc_msgs() -> = roster_client:send_receive(AClientId, Counter, #'Room'{status = create, type = group, id = Room, name = RoomName, admins = Admins, members = Members}), Counter = 2 + length(emqttd_server:subscribers(roster:muc_topic(APhoneId))), + + #'Message'{id = FirstMentionId, mentioned = [CId]} = roster_client:send_receive(AClientId, Counter, + #'Message'{feed_id = #muc{name = Room}, from = APhoneId, to = Room, mentioned = [CId], + files = [#'Desc'{id = <<"6">>, payload = <<"Test MUC member mention C!">>}], status = []}), + + #'Room'{mentions = [FirstMentionId]} = roster:room(roster:roster_id(CPhoneId), Room), + % Message from C #'Message'{id = FId} = roster_client:send_receive(CClientId, Counter, - #'Message'{feed_id = #muc{name = Room}, from = CPhoneId, to = Room, files = [#'Desc'{id = <<"7">>, payload = <<"Test MUC member C!">>}], status = []}), + #'Message'{feed_id = #muc{name = Room}, from = CPhoneId, to = Room, + files = [#'Desc'{id = <<"7">>, payload = <<"Test MUC member C!">>}], status = []}), {ok, #'Room'{readers = [CId, _AId]}} = kvs:get('Room', Room), %%TODO insert reader % Message from B #'Message'{id = BFid} = roster_client:send_receive(BClientId, Counter, #'Message'{feed_id = #muc{name = Room}, from = BPhoneId, to = Room, files = [#'Desc'{id = <<"7">>, payload = <<"TestB!">>}], status = []}), {ok, #'Room'{readers = [BId, CId]}} = kvs:get('Room', Room), #'Roster'{roomlist = [#'Room'{last_msg = #'Message'{id = BFid}}]} = roster:roster(roster:roster_id(APhoneId)), - + #'History'{} = roster_client:send_receive(DClientId, + #'History'{feed = #muc{name = Room}, roster_id = DPhoneId, size = 20, entity_id=0, status = get}), roster_client:send_receive(DClientId, last_res), roster_client:send_receive(DClientId, Counter + 1, #'History'{feed = #muc{name = Room}, roster_id = DPhoneId, entity_id = BFid, status = update}), {ok, #'Room'{readers = [BId, DId]}} = kvs:get('Room', Room), @@ -993,10 +1126,11 @@ test_muc_msgs() -> %% #'Message'{files = F} = roster_client:send_receive(BClientId, Counter, #'Message'{id=BFid, feed_id=#muc{name = Room}, %% from=BPhoneId, files = [#'Desc'{id= <<"7">>, payload = <<"Edited by B!">>}], status = edit}), - D = #'Desc'{id = <<"8">>, payload = <<"Тест A2!">>, mime = <<"translate">>, data = Data = [#'Feature'{key = ?LANG_KEY, value = <<"ru">>, group = <<"FILE_DATA">>}]}, + D = #'Desc'{id = <<"8">>, payload = <<"Тест A2!">>, mime = <<"translate">>, + data = [#'Feature'{id = ?LANG_KEY, key = ?LANG_KEY, value = <<"ru">>, group = <<"FILE_DATA">>}]}, % Message from B is updated #'Message'{files = [#'Desc'{mime = <<"text">>}, #'Desc'{mime = <<"translate">>}]} = roster_client:send_receive(BClientId, Counter, - #'Message'{id = BFid, feed_id = #muc{name = Room}, from = BPhoneId, + #'Message'{id = BFid, feed_id = #muc{name = Room}, from = BPhoneId, to = Room, files = [D#'Desc'{payload = <<"Тест B!">>}], status = update}), #'Message'{files = [#'Desc'{mime = <<"text">>}]} = roster_client:send_receive(BClientId, Counter, @@ -1107,12 +1241,13 @@ test_muc_msgs() -> roster_client:send_receive(CClientId, last_res), #'Message'{} = roster_client:send_receive(CClientId, Counter, - #'Message'{id=Id, feed_id = #muc{name = Room}, from = CPhoneId, files = [#'Desc'{id = <<"7">>, payload = <<"Edit Test MUC member C!">>}], status = edit}), + #'Message'{id=Id, feed_id = #muc{name = Room}, from = CPhoneId, to = Room, files = [#'Desc'{id = <<"7">>, payload = <<"Edit Test MUC member C!">>}], status = edit}), roster_client:send_receive(CClientId, Counter + 1, #'History'{feed = #muc{name = Room}, roster_id = CPhoneId, entity_id = Id2, status = update}), - [#'Message'{id = Id2, type = [read] - }, #'Room'{}] = roster_client:send_receive(CClientId, last_res), - #'Message'{id = Id3} = roster_client:send_receive(AClientId, Counter, #'Message'{feed_id = #muc{name = Room}, from = APhoneId, to = Room, - files = [#'Desc'{id = <<"7">>, payload = <<"TestA!">>}], status = []}), + [#'Message'{id = Id2, type = [read]}, #'Room'{}] = roster_client:send_receive(CClientId, last_res), + #'Message'{id = Id3} = + roster_client:send_receive(AClientId, Counter, + #'Message'{feed_id = #muc{name = Room}, from = APhoneId, to = Room, + files = [#'Desc'{id = <<"7">>, payload = <<"TestA!">>}], status = []}), %% Test cases for history % A gets history from Id to position -4 #'History'{data=[#'Message'{id = Id}, #'Message'{}]} @@ -1156,6 +1291,87 @@ test_muc_msgs() -> true = LastMsgId2 > LMId2, [roster_client:stop_client(ClientId) || {_, ClientId, _} <- PCs], ok. +test_call_room() -> + RoomName = Room = <<"test_room_call">>, + Phones = [{APhone = <<"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] = + [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), + Member = #'Member'{presence = online, feed_id = #muc{name = Room}, phone_id = PhoneId, status = Aff}, + {PhoneId, ClientId, Member} + end || {Phone, Aff} <- Phones], + _ = [C || {_, C, _, _} <- PCs], + ClientId = ?SYS_REST_CLIENT, + roster_client:start_client(ClientId), + {Admins, Members} = roster:split_members([M || {_, _, M} <- PCs]), + #'Room'{status = create, id = Room, + admins = [#'Member'{status = admin, presence = online, phone_id = APhoneId}, + #'Member'{status = admin, presence = online, phone_id = BPhoneId}], + members = [#'Member'{status = member, presence = online, phone_id = CPhoneId}, + #'Member'{status = member, presence = online, phone_id = DPhoneId}], type = group} + = roster_client:send_receive(ClientId, Counter, + #'Room'{status = create, type = group, id = Room, name = RoomName, admins = Admins, members = Members}), + roster:purge_room(Room), + #'Room'{status = create, id = Room, members = [], type = group, + admins = [#'Member'{status = admin, presence = online, phone_id = APhoneId}]} + = roster_client:send_receive(ClientId, 1, + #'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 = []} + = 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, + members = [#'Member'{status = member, phone_id = DPhoneId}], admins = [#'Member'{phone_id = APhoneId, status = admin}]} + = roster_client:send_receive(ClientId, 3, + #'Room'{status = add, type = group, id = Room, name = RoomName, admins = [AAdmin], members = [DMember]}), + + #'Room'{status = remove, id = Room, type = group, + members = [#'Member'{status = removed, phone_id = CPhoneId}], admins = [#'Member'{}]} + = roster_client:send_receive(ClientId, 3, + #'Room'{status = remove, type = group, id = Room, admins = [AAdmin], members = [CMember]}), + [{mqtt_subscriber, _, AClientId}, {_, _, DClientId}] = ets:lookup(mqtt_subscriber, roster:room_topic(Room)), + + #'Member'{status = removed} = roster:muc_member(CPhoneId, Room), + + #'Room'{status = join, id = Room, type = group, + members = [#'Member'{status = member, phone_id = CPhoneId}], admins = []} + = roster_client:send_receive(ClientId, 3, + #'Room'{status = join, type = group, id = Room, name = RoomName, admins = [], members = [CMember]}), + + #'Room'{status = delete, id = Room, type = group, members = [], admins = []} + = roster_client:send_receive(ClientId, 3, #'Room'{status = delete, type = group, id = Room}), + [] = roster:roomlist(roster:roster_id(AClientId)), + [] = ets:lookup(mqtt_subscriber, roster:room_topic(Room)), + + {ok, #'Room'{status = delete}} = kvs:get('Room', Room), + + roster:purge_room(Room), + #'Room'{status = create, id = Room, members = [#'Member'{phone_id = CPhoneId}], type = group, + admins = [#'Member'{status = admin, presence = online, phone_id = APhoneId}]} + = roster_client:send_receive(ClientId, 2, + #'Room'{status = create, type = group, id = Room, name = RoomName, admins = [AAdmin], members = [CMember]}), + + #'Message'{} = roster_client:send_receive(AClientId, 2, + #'Message'{feed_id = #muc{name = Room}, from = APhoneId, to = Room, files = [#'Desc'{id = <<"7">>, payload = <<"Test call message!">>}], status = []}), + + case catch roster_client:send_receive(ClientId, 1, #'Room'{status = delete, type = group, id = Room}, 200) of + {error, {timeout, _}} -> ok; + _ -> throw({error, invalid_check}) end, + + {ok, #'Room'{type = group}} = kvs:get('Room', Room), + {ok, #'Roster'{roomlist = Rooms}} = kvs:get('Roster', roster:roster_id(APhoneId)), + #'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(ClientId). + test_messages() -> [] = [M || #'Message'{id = Id, prev = P, next = N} = M <- kvs:all('Message'), Id > P, Id > N, P /= [], N /= []], [] = [M || #'Message'{id = Id, prev = P, next = N} = M <- kvs:all('Message'), Id < P, Id < N, P /= [], N /= []], ok. @@ -1221,7 +1437,8 @@ test_descs() -> || #'Roster'{roomlist = Rooms} = _ <- kvs:all('Roster')], ok. suite() -> - [test_roster(), + [ + test_roster(), test_update_contacts(), misc_test_roster(<<"777">>), test_reg(), @@ -1232,7 +1449,6 @@ suite() -> test_muc_msgs(), test_descs(), test_multi_muc(), - test_messages(), test_room(), clear_room_msg_test(), myself_chat_test(), @@ -1243,7 +1459,12 @@ suite() -> clear_test_only_admin(), reply_history_test(), history_update_test(), - muc_remove_test()]. + muc_remove_test(), + test_call_room(), + pagination_test(), + event_securty_test(), + test_messages() + ]. check() -> case lists:all(fun(X) -> X == ok end, suite()) of true -> roster:info("ALL TESTS PASSED", []), true; @@ -1303,7 +1524,7 @@ bpe() -> application:set_env(roster, job_delay, 1), #'History'{} = roster_client:send_receive(AClientId, #'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(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, container = chain, feed_id = #act{name= <<"publish">>, data=A}, data = [ #'Message'{feed_id = roster:feed_key(p2p, A, B), from = A, to = B, files = [#'Desc'{id = <<"3">>, payload = <<"TestA!">>}], status = []}] , status = init}), timer:sleep(1000), @@ -1315,7 +1536,7 @@ bpe() -> #'Desc'{id = <<"3">>, payload = <<"E TestA!">>}], status = []}), timer:sleep(1000), %#'Job'{ proc=Proc1}= - #'Message'{} = roster_client:send_receive(AClientId, #'Job'{time = [], feed_id = {act, <<"publish">>, A}, data = [ + #'Message'{} = roster_client:send_receive(AClientId, #'Job'{time = [], feed_id = #act{name= <<"publish">>, data=A}, data = [ #'Message'{id = EId, feed_id = roster:feed_key(p2p, A, B), from = A, to = B, %type=forward, files = [#'Desc'{id = <<"7">>, payload = list_to_binary([<<"777">> | [integer_to_binary(rand:uniform(99999999)) || _ <- lists:seq(1, 7)]])}], @@ -1336,7 +1557,7 @@ bpe() -> #'Message'{feed_id = roster:feed_key(p2p, A, B), from = A, to = B, files = [#'Desc'{id = <<"7">>, payload = <<"Next 2 TestA!">>}], status = []}] , status = init}), timer:sleep(2000), - #'Job'{proc = Proc} = roster_client:send_receive(AClientId, #'Job'{id = UpId, time = roster:now_msec() + 70000, feed_id = {act, <<"publish">>, A}, settings = ["New"], data = [ + #'Job'{proc = Proc} = roster_client:send_receive(AClientId, #'Job'{id = UpId, time = roster:now_msec() + 70000, feed_id = {act, <<"publish">>, A}, settings = [], data = [ #'Message'{feed_id = roster:feed_key(p2p, A, B), from = A, to = B, files = [#'Desc'{id = <<"7">>, payload = <<"Next 3 TestA!">>}], status = []}] , status = update}), [roster_client:send(AClientId, #'Job'{time = roster:now_msec() + 2000 + DT * 300, feed_id = {act, <<"publish">>, A}, data = @@ -1361,13 +1582,14 @@ test_room() -> %% Room2 = get_room_id(APhone, RoomName), Counter = length(Phones), roster:purge_room(RoomName), + Feed = #muc{name = Room}, PCs = [{_, AClientId, _, _} | [{BPhoneId, BClientId, _, _}, {CPhoneId, _, CM, _}, {DPhoneId, _, _, _}] = _] = [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, phone_id = PhoneId, status = Aff}, Token} + {PhoneId, ClientId, #'Member'{presence = online, feed_id = Feed, phone_id = PhoneId, status = Aff}, Token} end || {Phone, Aff} <- Phones], %% create and connect fake users ClientIds = [ClientId || {_, ClientId, _, _} <- PCs], roster_client:set_filer(ClientIds, filter), @@ -1403,8 +1625,9 @@ 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, 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]}), + 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), #'Room'{unread = 0} = roster:room(roster:roster_id(APhone), RoomID), #'Room'{unread = 1} = roster:room(roster:roster_id(BPhone), RoomID), @@ -1416,7 +1639,7 @@ test_room() -> roster:purge_user(FPhone = <<"6789000">>), {FClientId, FToken} = roster_client:reg_fake_user(FPhone), roster_client:start_cli_receive(FClientId, FToken), - FM = #'Member'{presence = online, phone_id = FPhoneId = roster:phone_id(FPhone), status = member}, + FM = #'Member'{presence = online, feed_id = Feed, phone_id = FPhoneId = roster:phone_id(FPhone), status = member}, SIZE = 2, #'Room'{status = add, last_msg = #'Message'{id = MesID}} = roster_client:send_receive(AClientId, Counter + 2, #'Room'{status = add, id = RoomID, admins = [FM], readers = [SIZE]}), @@ -1435,7 +1658,7 @@ test_room() -> 0 = length(NMsgs2), #'Room'{readers = [_|_]} = roster_client:send_receive(FClientId, #'Room'{id = RoomID, status = get}), - #'Room'{status = leave, members = [], admins = [#'Member'{phone_id = BPhoneId, status = admin}]} = + #'Room'{status = leave, members = [], admins = [#'Member'{feed_id = Feed, phone_id = BPhoneId, status = admin}]} = roster_client:send_receive(BClientId, 1, #'Room'{status = leave, id = Room}), [roster_client:stop_client(element(2, Ph)) || Ph <- PCs], @@ -1448,13 +1671,14 @@ clear_room_msg_test() -> %% Room2 = get_room_id(APhone, RoomName), Counter = length(Phones), roster:purge_room(RoomName), + Feed = #muc{name = Room}, PCs = [{APhoneId, AClientId, _, _} | [{BPhoneId, BClientId, _, _}, {CPhoneId, _, _, _}] = _] = [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, phone_id = PhoneId, status = Aff}, Token} + {PhoneId, ClientId, #'Member'{presence = online, feed_id = Feed, phone_id = PhoneId, status = Aff}, Token} end || {Phone, Aff} <- Phones], %% create and connect fake users ClientIds = [ClientId || {_, ClientId, _, _} <- PCs], roster_client:set_filer(ClientIds, filter), @@ -1471,7 +1695,7 @@ clear_room_msg_test() -> roster_client:send_receive(BClientId, Counter, #'History'{roster_id = BPhoneId, feed = MUC, status = delete}), %%write msg in chat roster_client:send_receive(BClientId, Counter, - #'Message'{feed_id = MUC, from = BPhoneId, files = [#'Desc'{id = <<"90909">>, payload = <<"Some Text">>}], status = []}), + #'Message'{feed_id = MUC, from = BPhoneId, to = Room, files = [#'Desc'{id = <<"90909">>, payload = <<"Some Text">>}], status = []}), %%clear msgs in chat roster_client:send_receive(BClientId, Counter, #'History'{roster_id = BPhoneId, feed = MUC, status = delete}), #'Roster'{} = roster:roster(roster:roster_id(BPhoneId)), @@ -1503,7 +1727,7 @@ history_update_test() -> {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, phone_id = PhoneId, status = Aff}, Token} + {PhoneId, ClientId, #'Member'{presence = online, feed_id = #muc{name = Room}, phone_id = PhoneId, status = Aff}, Token} end || {Phone, Aff} <- Phones], %% create and connect fake users ClientIds = [ClientId || {_, ClientId, _, _} <- PCs], roster_client:set_filer(ClientIds, filter), @@ -1519,7 +1743,7 @@ history_update_test() -> roster_client:receive_last(BClientId, Counter + 1, #'History'{feed = #muc{name = Room}, roster_id = BPhoneId, entity_id = MsgId, status = update}), %%user B send msg in chat #'Message'{id = MsgId0} = roster_client:send_receive(BClientId, Counter, - #'Message'{feed_id = #muc{name = Room}, from = BPhoneId, + #'Message'{feed_id = #muc{name = Room}, from = BPhoneId, to = Room, files = [#'Desc'{id = <<"90909">>, payload = <<"Some Text">>}], status = []}), #'Room'{readers = [MsgId0, MsgId]} = roster:room(roster:roster_id(BPhoneId), RoomName), @@ -1529,7 +1753,7 @@ history_update_test() -> files = [#'Desc'{id = <<"90910">>, payload = <<"Some Text 2">>}], status = []}), %% delete msg #'Message'{id = DMsgId} = roster_client:send_receive(BClientId, Counter, - #'Message'{id = MsgID, seenby = [-1], feed_id = #muc{name = Room}, from = BPhoneId, status = delete}), + #'Message'{id = MsgID, seenby = [-1], feed_id = #muc{name = Room}, from = BPhoneId, to = Room, status = delete}), %% get user last msg in chat #'Room'{last_msg = #'Message'{id = MsgID2, status = update}} = roster:room(roster:roster_id(CPhoneId), RoomID), %%get History with deleted message @@ -1549,7 +1773,7 @@ history_update_test() -> %remove user C from room roster_client:send_receive(AClientId, last_res), roster_client:send_receive(AClientId, Counter-1, #'Room'{id = Room, members = - [#'Member'{id = CMId, phone_id = CPhoneId, status = member}], status = remove}), + [#'Member'{id = CMId, feed_id = #muc{name = Room}, phone_id = CPhoneId, status = member}], status = remove}), [#'Message'{id = MsgID6}, #'Room'{readers = [MsgID6, MsgID4]}] = roster_client:send_last(AClientId, 2), [roster_client:stop_client(element(2, Ph)) || Ph <- PCs], ok. @@ -1565,7 +1789,7 @@ clear_test_only_admin() -> {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, phone_id = PhoneId, status = Aff}, Token} + {PhoneId, ClientId, #'Member'{presence = online, feed_id = #muc{name = Room}, phone_id = PhoneId, status = Aff}, Token} end || {Phone, Aff} <- Phones], %% create and connect fake users Admins = [M || {_, _, M, _} <- PCs], #'Room'{status = create, last_msg = #'Message'{type = [sys]}, @@ -1602,7 +1826,10 @@ get_reader(#muc{name = _Room} = Feed, RosterId) when is_integer(RosterId) -> get_reader(Feed, Phone); get_reader(#muc{name = Room} = Feed, Phone) -> #'Member'{reader = ReaderId} = roster:muc_member(roster:phone_id(Phone), Room), - {kvs_stream:load_reader(ReaderId), kvs_stream:load_writer(Feed)}. + {kvs_stream:load_reader(ReaderId), kvs_stream:load_writer(Feed)}; +get_reader(#p2p{} = Feed, RosterId) -> + {kvs_stream:load_reader(roster:get_reader(Feed, RosterId)), + kvs_stream:load_writer(Feed)}. delete_p2p_history_test() -> roster:purge_user(APhone = <<"2222">>), @@ -1868,7 +2095,7 @@ muc_remove_test() -> {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, phone_id = PhoneId, status = Aff}, Token} + {PhoneId, ClientId, #'Member'{presence = online, feed_id = #muc{name = Room}, phone_id = PhoneId, status = Aff}, Token} end || {Phone, Aff} <- Phones], Admins = [M || {_, _, M, _} <- PCs], #'Room'{status = create, last_msg = #'Message'{type = [sys]}, type = group, members = [CMem = #'Member'{}], @@ -1881,13 +2108,236 @@ muc_remove_test() -> #'Message'{} = roster_client:send_receive(BClientId, Counter, #'Message'{feed_id = Muc, from = BPhoneId, to = Room, files = [#'Desc'{id = <<"9091qw0">>, payload = <<"Some Text 23">>}], status = []}), % remove user C from room by user A - [#'Message'{msg_id = MsgId}, #'Room'{members = [#'Member'{status = removed}], admins = [#'Member'{status = admin}], status = remove}] = + [#'Message'{id = MsgId}, #'Room'{members = [#'Member'{status = removed}], admins = [#'Member'{status = admin}], status = remove}] = roster_client:receive_last2(AClientId, Counter, #'Room'{status = remove, id = Room, members = [CMem]}, 2), roster_client:send_receive([BClientId, CClientId], last_res), % user C update history [#'Room'{unread = 0}] = roster_client:receive_last2(CClientId, Counter + 1, #'History'{feed = Muc, roster_id = CPhoneId, entity_id = MsgId, status = update}, 2), %% clear msgs in chat #'Message'{} = roster_client:send_receive(AClientId, Counter - 1, #'History'{roster_id = APhoneId, feed = Muc, status = delete}), - #'Room'{status = removed, last_msg = #'Message'{msg_id = MsgId}} = roster:room(roster:roster_id(CPhoneId), Room), + #'Room'{status = removed, last_msg = #'Message'{id = MsgId}} = roster:room(roster:roster_id(CPhoneId), Room), [roster_client:stop_client(element(2, Ph)) || Ph <- PCs], - ok. \ No newline at end of file + ok. + +test_link_desc() -> + Payload = <<"dddd cryoflamer@ddd.com fffff cccc@ffff.vov\n " + "33\n https://www.youtube.com/watch?v=0YCmjyRtNEc \nffff" + " https://www.facebook.com/vitaly.panich/videos/1405876049544768/" + "[+79082343434 \n 8(912)2342554', '8-923-132-34-23', '+7 982 342 34 34]">>, + Desc = #'Desc'{id = <<"DescID">>, payload = Payload, mime = <<"text">>}, + roster:parse_desc(Desc). + +pagination_contact_test(N, UserCount) -> + Prefix = <<"PAGINATE">>, + case catch roster:roster_id(<>) of + {'EXIT', _} -> skip; + Id -> {ok, #'Roster'{roomlist = Rooms}} = kvs:get('Roster', Id), + [roster:purge_room(Room) || #'Room'{id = Room}<-Rooms] + end, + + [roster:purge_user(<>) || I <- lists:seq(1, UserCount)], + 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", []), + [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", []), + #'Roster'{userlist = Users}= roster:roster(roster:roster_id(PhoneId)), + UserCount = length(Users), + roster:info(?MODULE, "roster is got", []), + {ok, #'Roster'{} = StoredRoster} = kvs:get('Roster', roster:roster_id(PhoneId)), + PageCounter = case UserCount div N of Count when UserCount/N == Count -> Count; Count -> Count + 1 end, + [#'Roster'{userlist = Cs}|TRosters] = Rosters = lists:reverse(roster:split_roster(StoredRoster, N, 0, [])), + roster:info(?MODULE, "roster is splitted", []), + Rem = UserCount rem N, + Rem = length(Cs), + PageCounter = length(Rosters), + [N = length(Contacts) || #'Roster'{userlist = Contacts} <-TRosters], + Users2. +pagination_test() -> + N = 3, UserCount = 10, RoomCount = 7, + Users = [{ClientId, PhoneId, Token}|TUsers] = pagination_contact_test(N, UserCount), + timer:sleep(100), + Creates = [begin + roster_client:test_info(roster_message, + #'Message'{status = [], feed_id = Feed = roster:feed_key(p2p, PhoneId, PId), from = PhoneId, to = PId, + files = [#'Desc'{id = <<"P17">>, payload = <<"PaginationTest! ", PId/binary>>}]}, #cx{params = ClientId}), + #writer{cache = Msg = #'Message'{created = Created}} = kvs_stream:load_writer(Feed), + roster_client:test_info(roster_favorite, #'Star'{status = add, message = Msg}, #cx{params = ClientId}), + timer:sleep(100), + Created + end || {_, PId, _} <- TUsers], + LastMsgCreated = lists:last(Creates)-1, + {_, LastPhoneId, _} = lists:last(TUsers), + {ok, #'Roster'{userlist = Contacts, favorite = Stars} = StoredRoster} = kvs:get('Roster', roster:roster_id(PhoneId)), + UserCount = length(Stars)+1, + UserCount = length(Contacts), + [#'Roster'{userlist = [_,_,_], favorite = [_,_,_]}, + #'Roster'{userlist = [_,_,_], favorite = [_,_,_]}, + #'Roster'{userlist = [_,_,_], favorite = [_,_,_]}, + #'Roster'{userlist = [_] , favorite = []}] + = roster:split_roster(StoredRoster, N, 0, []), + + [#'Roster'{userlist = [#'Contact'{phone_id = LastPhoneId}], + favorite = [#'ExtendedStar'{from = #'Contact'{phone_id = PhoneId}}]}] + = roster:split_roster(StoredRoster, N, LastMsgCreated, []), + MsgCreated = lists:nth(5, Creates), + [#'Roster'{userlist = [_, _, _], favorite = [_, _, _]}, + #'Roster'{userlist = [_, _], favorite = [_,_]}] + = roster:split_roster(StoredRoster, N, MsgCreated-1, []), + timer:sleep(10), + TimeBefore = roster:now_msec(), + StoredRoster2 = pagination_room_test(Users, RoomCount), + [#'Roster'{roomlist = [_, _, _]}, #'Roster'{roomlist = [_, _, _]}, #'Roster'{roomlist = [_]}] + = roster:split_roster(StoredRoster2, N, LastMsgCreated, []), + + [#'Roster'{favorite = [_, _, _], userlist = [], roomlist = []}, %% verify room star messages + #'Roster'{favorite = [_, _, _], userlist = [], roomlist = []}, + #'Roster'{favorite = [_], userlist = [], roomlist = []}] + = roster:split_roster(StoredRoster2#'Roster'{userlist = [], roomlist = []}, N, TimeBefore, []), + + [_, _, _] = roster:split_objlist(StoredRoster2#'Roster'.favorite, StoredRoster2, TimeBefore, N, []), + FavsList = roster:split_objlist(StoredRoster2#'Roster'.favorite, StoredRoster2, 0, N, []), + 6 = length(FavsList), + [Fs] = roster:split_objlist(StoredRoster2#'Roster'.favorite, StoredRoster2, 0, [], []), + UserCount = length(Fs)-RoomCount+1, + #'Profile'{} = roster_client:start_cli_receive(ClientId, Token), + ExStars = [#'ExtendedStar'{}|_] = roster_client:send_receive(ClientId, #'ExtendedStar'{}), + UserCount = length(ExStars)-RoomCount+1, + #'Profile'{status = get, rosters = [#'Roster'{}]} + = roster_client:send_receive(ClientId, #'Profile'{status = get, update = LastMsgCreated}), + + roster_client:send_receive(ClientId, unsort_last_res), + #'Profile'{status = get} + = roster_client:send_receive(ClientId, 3, #'Profile'{status = get, update = LastMsgCreated, + settings = [#'Feature'{key= <<"size">>, value = <<"3">>, group = <<"PAGINATION">>}]}), + SplittedProfile = [#'Profile'{status = get, rosters = [#'Roster'{}]}, + #'Profile'{status = get, rosters = [#'Roster'{}]}, + #'Profile'{status = get, rosters = [#'Roster'{}]}] + = roster_client:send_receive(ClientId, unsort_last_res), + + [true = Cr > LastMsgCreated + || #'Profile'{rosters = [#'Roster'{roomlist = Rooms}]} <- SplittedProfile, + #'Room'{last_msg = #'Message'{created = Cr}}<-Rooms], + [true = Cr > LastMsgCreated + || #'Profile'{rosters = [#'Roster'{favorite = Stars}]} <- SplittedProfile, + #'ExtendedStar'{star = #'Message'{created = Cr}}<-Stars], + [true = Cr > LastMsgCreated + || #'Profile'{rosters = [#'Roster'{userlist = Contacts}]} <- SplittedProfile, + #'Contact'{last_msg = #'Message'{created = Cr}}<-Contacts], + {ClientId2, PhoneId2, Token2} = hd(TUsers), + #'Profile'{} = roster_client:start_cli_receive(ClientId2, Token2), + #'Contact'{phone_id = PhoneId2, presence = online} = roster:user(RosterId = roster:roster_id(PhoneId), PhoneId2), + #'Roster'{userlist = {[#'Contact'{}, #'Contact'{}], []}} = roster:roster(RosterId, 3, roster:now_msec()), + #'Profile'{status = get, rosters = [#'Roster'{userlist = + [#'Contact'{phone_id = PhoneId2, presence = online}, #'Contact'{phone_id = PhoneId, presence = online}]}]} + = roster_client:send_receive(ClientId, #'Profile'{status = get, update = roster:now_msec(), + settings = [#'Feature'{key= <<"size">>, value = <<"3">>, group = <<"PAGINATION">>}]}), + lists:map(fun roster_client:stop_client/1, [ClientId, ClientId2]), ok. + +pagination_room_test([{ClientId, PhoneId, _Token}|TUsers], RoomCount) -> + [begin + Room = <<"room_pagination_", (integer_to_binary(I))/binary>>, + roster_client:test_info(roster_room, + #'Room'{status = create, type = group, id = Room, name = Room, + admins = [#'Member'{feed_id = #muc{name = Room}, alias = roster:phone(PhoneId), presence = online, phone_id = PhoneId, status = admin}], + members = [#'Member'{feed_id = #muc{name = Room}, alias = roster:phone(PId), presence = online, phone_id = PId, status = member} + || {_, PId, _}<- TUsers]}, + #cx{params = ClientId}), Room + end || I <- lists:seq(1, RoomCount)], + {ok, #'Roster'{roomlist = Rooms}} = kvs:get('Roster', RosterId = roster:roster_id(PhoneId)), + RoomCount = length(Rooms), + [begin roster_client:test_info(roster_message, + #'Message'{status = [], feed_id = #muc{name = Room}, from = PhoneId, to = Room, + files = [#'Desc'{id = <<"P20">>, payload = <<"PaginationRoomTest!">>}]}, #cx{params = ClientId}), + #writer{cache = Msg} = kvs_stream:load_writer(#muc{name = Room}), + roster_client:test_info(roster_favorite, #'Star'{status = add, message = Msg}, #cx{params = ClientId}), + timer:sleep(100) + end || #'Room'{id = Room} <- Rooms], + element(2, kvs:get('Roster', RosterId)). + +thumb_test_verify() -> + Descs = [Thumb1 = #'Desc'{id = <<"1">>, parentid = <<"2">>, mime = <<"thumb">>}, + Media1 = #'Desc'{id = <<"2">>, parentid = <<>>, mime = <<"image">>}, + Desc3 = #'Desc'{id = <<"3">>}], + {[], []} = roster:verify_thumbs(Descs), + false = {[], []} == roster:verify_thumbs([Thumb1#'Desc'{parentid = <<"3">>} | Descs]), + false = {[], []} == roster:verify_thumbs([Desc3#'Desc'{mime = <<"video">>} | Descs]), + false = {[], []} == roster:verify_thumbs([Thumb1, Media1#'Desc'{mime = <<"text">>}, Desc3]). + +event_securty_test() -> %% security verification for "events/..." topic + Phones = [PhoneA, _PhoneB] = [<<"eventtestA">>, <<"eventtestB">>], + [kvs:put(#'Whitelist'{phone = Phone, created = roster:now_msec()}) || Phone <- Phones], + [roster:purge_user(Phone) || Phone <- Phones], + [{_APhoneId, AClientId, _AToken}, {_BPhoneId, BClientId, _BToken}] = + [begin + {ClientId, Token} = roster_client:reg_fake_user(Phone), + roster_client:start_cli_receive(ClientId, Token), + [Roster] = roster_client:rosters(ClientId, Phone), {Roster, ClientId, Token} + end || Phone <- Phones], + SendFun = fun(#state{mqttc = C} = State) -> + roster:send_event(C, BClientId, <<>>, #'Profile'{status = get},{qos,0}), State end, + + [roster_client:send_receive(ClientId, last_res) || ClientId <- [AClientId, BClientId]], + [#'Profile'{phone = PhoneA, status = get}] = roster_client:receive_last(AClientId, 1, {callback, SendFun}), + [] = roster_client:send_receive(BClientId, last_res), + + roster_client:stop_client([AClientId, BClientId]). + +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}] = + [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}]), + {roster:phone_id(Phone, RosterId), ClientId, Roster} + end || Phone <- Phones], + case Fs of + [_] -> roster_client:set_filer([AClientId, BClientId], filter_friend), + roster_client:send_receive(AClientId, 2, #'Friend'{phone_id = APhoneId, friend_id = BPhoneId, status = request}), + roster_client:send_receive(BClientId, 3, #'Friend'{phone_id = BPhoneId, friend_id = APhoneId, status = confirm}), + roster_client:set_filer([AClientId, BClientId], filter); + _ -> ok + end, + Feed = roster:feed_key(p2p, APhoneId, BPhoneId), + SubscribeFun = + fun(Subscr) -> + fun(#state{mqttc = C, receive_pid = Self} = State) -> + emqttc:Subscr(C, roster:p2p_topic(Feed)), +% emqttc:Subscr(C,iolist_to_binary(["#"])), + Self ! Subscr, State + end + end, + + subscribe = roster_client:send_receive(CClientId, 1, {callback, SubscribeFun(subscribe)}), + roster_client:send_receive(CClientId, last_res), + case catch roster_client:send_receive(AClientId, 3, #'Message'{feed_id = Feed, from = APhoneId, to = BPhoneId, + files = [#'Desc'{id = <<"84">>, payload = <<"SecuritySubscribeTest!">>}], status = []}, 1000) of + #'Message'{} -> + case roster_client:send_receive(CClientId, last_res) of + [#'Message'{}] -> + unsubscribe = roster_client:send_receive(CClientId, 1, {callback, SubscribeFun(unsubscribe)}), + throw({error, unsecurity_subscribe}); + [] -> ok + end; + {error, {timeout, _}} -> ok + end, + + SendFun = fun(#state{mqttc = C} = State) -> + roster:send_p2p(C, Feed,#'Message'{feed_id = Feed, from = APhoneId, to = BPhoneId, + files = [#'Desc'{id = <<"88">>, payload = <<"SecurityPublishTest!">>}], status = []}), State end, + + 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]), + throw({error, unexpected}) + end, +%% #'History'{data = [_, _]} %% only two message in history +%% = roster_client:send_receive(AClientId, #'History'{feed = Feed, roster_id = APhoneId, size = [], entity_id = [], status = get}), + roster_client:stop_client([AClientId, BClientId, CClientId]). \ No newline at end of file diff --git a/apps/roster/static/main_var.hrl b/apps/roster/static/main_var.hrl deleted file mode 100644 index cceca5b0867643227baa656458396a402927133b..0000000000000000000000000000000000000000 --- a/apps/roster/static/main_var.hrl +++ /dev/null @@ -1,5 +0,0 @@ -%% ------------------------------------------------------------------ -%% Variables common for multiple modules -%% ------------------------------------------------------------------ - --define(SYS_MSG_TYPE, [sys]). \ No newline at end of file diff --git a/apps/roster/static/rest_text.hrl b/apps/roster/static/rest_text.hrl deleted file mode 100644 index 99f67929e7e45af40ee5cc43e75af3d693bbf4c5..0000000000000000000000000000000000000000 --- a/apps/roster/static/rest_text.hrl +++ /dev/null @@ -1,21 +0,0 @@ -%% ------------------------------------------------------------------ -%% Errors and info messages for rest modules -%% ------------------------------------------------------------------ - --define(TEST_API_ENDPOINT, "/test"). - --define(RESPONSE_200, <<"Success">>). - --define(ERROR_400, <<"Bad Request">>). --define(ERROR_401, <<"Unauthorized">>). --define(ERROR_404, <<"Not Found">>). --define(ERROR_405, <<"Method Not Allowed">>). --define(ERROR_INVALID_JSON, <<"Invalid Request Json">>). --define(ERROR_MISSING_PARAM, <<"Missing Request Param">>). - -%% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -%% Rest Publish Module -%% - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - --define(ERROR_INVALID_QOS_PARAM, <<"Invalid 'qos' Param. Valid Values: 0, 1, 2">>). --define(ERROR_INVALID_MESSAGE_PARAM, <<"Invalid 'message' Param. Should be base64 encoded bytearray">>). \ No newline at end of file diff --git a/apps/service/include/auth.hrl b/apps/service/include/auth.hrl index 7c8a358b484d0c1e2ed4651b3074daed2dbd4bdf..0f7aa4717067703ebfe5c5c187afed265df2897c 100644 --- a/apps/service/include/auth.hrl +++ b/apps/service/include/auth.hrl @@ -1,3 +1,4 @@ +-include("feature.hrl"). -ifndef(AUTH_HRL). -define(AUTH_HRL, true). diff --git a/apps/service/include/room.hrl b/apps/service/include/room.hrl index a6ec4e5b5a438f4eaa935556f0c87a45c8a1e5c4..67216b2bcc11126bc6ac8637709e89087431ac6b 100644 --- a/apps/service/include/room.hrl +++ b/apps/service/include/room.hrl @@ -4,19 +4,9 @@ -type roomType() :: group | channel. -type linkStatus() :: lgen | lcheck | ladd | ldelete | lupdate. -type memberStatus() :: admin | member | removed | patch | owner. --type container() :: chain | cur. -type roomStatus() :: room_create | room_leave| room_add | room_remove | room_patch | room_get | room_delete | room_last_msg. --record(muc, {name = [] :: [] | binary() }). --record(p2p, {from = [] :: [] | binary(), - to = [] :: [] | binary() }). - --record('Link', {id = [] :: [] | binary(), - name = [] :: [] | binary(), - room_id = [] :: [] | binary(), - created = 0 :: [] | integer(), - links_status = [] :: linkStatus()}). -record('Member', {id = [] :: [] | integer(), container = chain :: container(), @@ -36,23 +26,29 @@ presence = offline :: presence(), member_status = member :: memberStatus()}). +-record('Link', {entity_id = [] :: [] | binary(), %% Room id etc + name = [] :: [] | binary(), %% actually the link + type = [] :: [] | group | channel, + status = [] :: [] | get | add | delete | update }). + -record('Room', {id = [] :: [] | binary(), - name = [] :: [] | binary(), - links = [] :: [] | list(#'Link'{}), - description = [] :: [] | binary(), - settings = [] :: list(#'Feature'{}), - members = [] :: list(#'Member'{}), - admins = [] :: list(#'Member'{}), - data = [] :: list(#'Desc'{}), - type = [] :: roomType(), - tos = [] :: [] | binary(), - tos_update = 0 :: [] | integer(), - unread = 0 :: [] | integer(), - mentions = [] :: list(integer()), - readers = [] :: list(integer()), - last_msg = [] :: [] | integer(), % #'Message'{} - update = 0 :: [] | integer(), - created = 0 :: [] | integer(), - status = [] :: roomStatus()}). + name = [] :: [] | binary(), + links = [] :: [] | binary(), + description = [] :: [] | binary(), + settings = [] :: list(#'Feature'{}), + members = [] :: list(#'Member'{}), + admins = [] :: list(#'Member'{}), + data = [] :: list(#'Desc'{}), + type = [] :: [] | group | channel, + tos = [] :: [] | binary(), + tos_update = 0 :: [] | integer(), + unread = 0 :: [] | integer(), + mentions = [] :: list(integer()), + readers = [] :: list(integer()), + last_msg = [] :: [] | integer() | #'Message'{}, + update = 0 :: [] | integer(), + created = 0 :: [] | integer(), + status = [] :: [] | create | leave| add | remove | removed | join + | patch | get | delete | last_msg | mute | unmute}). -endif. diff --git a/apps/service/src/service.app.src b/apps/service/src/service.app.src index bef572d8d47928d265b88de3bf1c6ab6a4caf587..817f38f8592724cb044f45a05c7ee77037f95169 100644 --- a/apps/service/src/service.app.src +++ b/apps/service/src/service.app.src @@ -2,6 +2,6 @@ [{description, "NYNJA Protocol 2.0"}, {vsn, "1"}, {registered, []}, - {applications, [kernel,stdlib,bert]}, + {applications, [kernel,stdlib,roster,n2o,bert]}, {mod, { service, []}}, {env, []}]}. diff --git a/apps/service/src/service_room.erl b/apps/service/src/service_room.erl index 39073346da741ce53bf72e6925b7f1b2ddfda5b0..7138f5d8b631902487e2a1c62f5e5c5536020e60 100644 --- a/apps/service/src/service_room.erl +++ b/apps/service/src/service_room.erl @@ -1,5 +1,6 @@ -module(service_room). -include_lib("service/include/feature.hrl"). +-include_lib("service/include/message.hrl"). -include_lib("service/include/room.hrl"). -compile({parse_transform, bert_google}). diff --git a/bin/emqttd_ctl b/bin/emqttd_ctl new file mode 100755 index 0000000000000000000000000000000000000000..7ceef10c30518078c5c7fc632ce57d90277c94ff --- /dev/null +++ b/bin/emqttd_ctl @@ -0,0 +1,98 @@ +#!/bin/sh +# -*- tab-width:4;indent-tabs-mode:nil -*- +# ex: ts=4 sw=4 et + +set -e + +SCRIPT=$(readlink $0 || true) +if [ -z $SCRIPT ]; then + SCRIPT=$0 +fi; +SCRIPT_DIR="$(cd /home/nynja/server/bin && pwd -P)" +RUNNER_ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd -P)" +REL_NAME="emqttd" +REL_VSN="2.3.9" +ERTS_VSN="9.2" +REL_DIR="$RUNNER_ROOT_DIR" +ERL_OPTS="" +RUNNER_LOG_DIR=$RUNNER_ROOT_DIR/log +RUNNER_LIB_DIR=$RUNNER_ROOT_DIR/deps +RUNNER_ETC_DIR=$RUNNER_ROOT_DIR/etc +RUNNER_DATA_DIR=$RUNNER_ROOT_DIR + +find_erts_dir() { + __erts_dir="$RUNNER_ROOT_DIR/erts-$ERTS_VSN" + if [ -d "$__erts_dir" ]; then + ERTS_DIR="$__erts_dir"; + ROOTDIR="$RUNNER_ROOT_DIR" + else + __erl="$(which erl)" + code="io:format(\"~s\", [code:root_dir()]), halt()." + __erl_root="$("$__erl" -noshell -eval "$code")" + ERTS_DIR="$__erl_root/erts-$ERTS_VSN" + ROOTDIR="$__erl_root" + fi +} + +relx_get_nodename() { + id="longname$(relx_gen_id)-${NAME}" + "$BINDIR/erl" -boot start_clean -eval '[Host] = tl(string:tokens(atom_to_list(node()),"@")), io:format("~s~n", [Host]), halt()' -noshell ${NAME_TYPE} $id +} + +# Control a node +relx_nodetool() { + command="$1"; shift + + ERL_FLAGS="$ERL_FLAGS $PROTO_DIST_ARG" \ + "$ERTS_DIR/bin/escript" "$ROOTDIR/bin/nodetool" "$NAME_TYPE" "$NAME" \ + -setcookie "$COOKIE" "$command" $@ +} + + +[ "x" = "x$EMQ_NODE_NAME" ] || NAME_ARG="-name $EMQ_NODE_NAME" +# Extract the target node name from node.args +if [ -z "$NAME_ARG" ]; then + NODENAME=`egrep '^[ \t]*node.name[ \t]*=[ \t]*' $RUNNER_ETC_DIR/emq.conf 2> /dev/null | tail -1 | cut -d = -f 2-` + if [ -z "$NODENAME" ]; then + echoerr "vm.args needs to have a -name parameter." + echoerr " -sname is not supported." + exit 1 + else + NAME_ARG="-name ${NODENAME# *}" + fi +fi + +# Extract the name type and name from the NAME_ARG for REMSH +NAME_TYPE="$(echo "$NAME_ARG" | awk '{print $1}')" +NAME="$(echo "$NAME_ARG" | awk '{print $2}')" + +[ "x" = "x$EMQ_NODE_COOKIE" ] || COOKIE_ARG="-setcookie $EMQ_NODE_COOKIE" +# Extract the target cookie +if [ -z "$COOKIE_ARG" ]; then + COOKIE=`egrep '^[ \t]*node.cookie[ \t]*=[ \t]*' $RUNNER_ETC_DIR/emq.conf 2> /dev/null | tail -1 | cut -d = -f 2-` + if [ -z "$COOKIE" ]; then + echoerr "vm.args needs to have a -setcookie parameter." + exit 1 + else + COOKIE_ARG="-setcookie $COOKIE" + fi +fi + +# Extract cookie name from COOKIE_ARG +COOKIE="$(echo "$COOKIE_ARG" | awk '{print $2}')" + +# Support for IPv6 Dist. See: https://github.com/emqtt/emqttd/issues/1460 +PROTO_DIST=`egrep '^[ \t]*node.proto_dist[ \t]*=[ \t]*' $RUNNER_ETC_DIR/emq.conf 2> /dev/null | tail -1 | cut -d = -f 2-` +if [ -z "$PROTO_DIST" ]; then + PROTO_DIST_ARG="" +else + PROTO_DIST_ARG="-proto_dist $PROTO_DIST" +fi + +find_erts_dir +export ROOTDIR="$RUNNER_ROOT_DIR" +export BINDIR="$ERTS_DIR/bin" +cd "$ROOTDIR" + +relx_nodetool rpc emqttd_ctl run $@ + diff --git a/bin/nodetool b/bin/nodetool new file mode 100755 index 0000000000000000000000000000000000000000..1b39f129ce97455088f86cdfe08c37db9a00a287 --- /dev/null +++ b/bin/nodetool @@ -0,0 +1,296 @@ +#!/usr/bin/env escript +%% -*- mode: erlang;erlang-indent-level: 4;indent-tabs-mode: nil -*- +%% ex: ft=erlang ts=4 sw=4 et +%% ------------------------------------------------------------------- +%% +%% nodetool: Helper Script for interacting with live nodes +%% +%% ------------------------------------------------------------------- +-mode(compile). + +main(Args) -> + ok = start_epmd(), + %% Extract the args + {RestArgs, TargetNode} = process_args(Args, [], undefined), + + %% process_args() has side-effects (e.g. when processing "-name"), + %% so take care of app-starting business first. + [application:start(App) || App <- [crypto, public_key, ssl]], + + %% any commands that don't need a running node + case RestArgs of + ["mnesia_dir", DataDir, NodeName] -> + create_mnesia_dir(DataDir, NodeName), halt(0); + ["mergeconf", EmqCfg, PluginsEtc, DestDir] -> + mergeconf(EmqCfg, PluginsEtc, DestDir); + ["chkconfig", File] -> + chkconfig(File); + ["chkconfig", "-config", File|_] -> + chkconfig(File); + _ -> + ok + end, + + %% See if the node is currently running -- if it's not, we'll bail + case {net_kernel:hidden_connect_node(TargetNode), net_adm:ping(TargetNode)} of + {true, pong} -> + ok; + {false, pong} -> + io:format(standard_error, "Failed to connect to node ~p .\n", [TargetNode]), + halt(1); + {_, pang} -> + io:format(standard_error, "Node ~p not responding to pings.\n", [TargetNode]), + halt(1) + end, + + case RestArgs of + ["getpid"] -> + io:format("~p\n", [list_to_integer(rpc:call(TargetNode, os, getpid, []))]); + ["ping"] -> + %% If we got this far, the node already responsed to a ping, so just dump + %% a "pong" + io:format("pong\n"); + ["stop"] -> + rpc:call(TargetNode, emqttd_plugins, unload, [], 60000), + io:format("~p\n", [rpc:call(TargetNode, init, stop, [], 60000)]); + ["halt"] -> + rpc:call(TargetNode, roster, save_db, ["Mnesia.ekka@127.0.0.1/db"], 60000), + io:format("~p\n", [rpc:call(TargetNode, init, stop, [], 60000)]); + ["restart"] -> + io:format("~p\n", [rpc:call(TargetNode, init, restart, [], 60000)]); + ["reboot"] -> + io:format("~p\n", [rpc:call(TargetNode, init, reboot, [], 60000)]); + ["rpc", Module, Function | RpcArgs] -> + case rpc:call(TargetNode, list_to_atom(Module), list_to_atom(Function), + [RpcArgs], 60000) of + ok -> + ok; + {error, cmd_not_found} -> + halt(1); + {error, Reason} -> + io:format("RPC to ~s error: ~p\n", [TargetNode, Reason]), + halt(1); + {badrpc, Reason} -> + io:format("RPC to ~s failed: ~p\n", [TargetNode, Reason]), + halt(1); + _ -> + halt(1) + end; + ["rpc_infinity", Module, Function | RpcArgs] -> + case rpc:call(TargetNode, list_to_atom(Module), list_to_atom(Function), [RpcArgs], infinity) of + ok -> + ok; + {badrpc, Reason} -> + io:format("RPC to ~p failed: ~p\n", [TargetNode, Reason]), + halt(1); + _ -> + halt(1) + end; + ["rpcterms", Module, Function | ArgsAsString] -> + case rpc:call(TargetNode, list_to_atom(Module), list_to_atom(Function), + consult(lists:flatten(ArgsAsString)), 60000) of + {badrpc, Reason} -> + io:format("RPC to ~p failed: ~p\n", [TargetNode, Reason]), + halt(1); + Other -> + io:format("~p\n", [Other]) + end; + ["eval" | ListOfArgs] -> + % shells may process args into more than one, and end up stripping + % spaces, so this converts all of that to a single string to parse + String = binary_to_list( + list_to_binary( + join(ListOfArgs," ") + ) + ), + + % then just as a convenience to users, if they forgot a trailing + % '.' add it for them. + Normalized = + case lists:reverse(String) of + [$. | _] -> String; + R -> lists:reverse([$. | R]) + end, + + % then scan and parse the string + {ok, Scanned, _} = erl_scan:string(Normalized), + {ok, Parsed } = erl_parse:parse_exprs(Scanned), + + % and evaluate it on the remote node + case rpc:call(TargetNode, erl_eval, exprs, [Parsed, [] ]) of + {value, Value, _} -> + io:format ("~p\n",[Value]); + {badrpc, Reason} -> + io:format("RPC to ~p failed: ~p\n", [TargetNode, Reason]), + halt(1) + end; + Other -> + io:format("Other: ~p\n", [Other]), + io:format("Usage: nodetool {genconfig, chkconfig|getpid|ping|stop|restart|reboot|rpc|rpc_infinity|rpcterms|eval [Terms]} [RPC]\n") + end, + net_kernel:stop(). + +process_args([], Acc, TargetNode) -> + {lists:reverse(Acc), TargetNode}; +process_args(["-setcookie", Cookie | Rest], Acc, TargetNode) -> + erlang:set_cookie(node(), list_to_atom(Cookie)), + process_args(Rest, Acc, TargetNode); +process_args(["-name", TargetName | Rest], Acc, _) -> + ThisNode = append_node_suffix(TargetName, "_maint_"), + {ok, _} = net_kernel:start([ThisNode, longnames]), + process_args(Rest, Acc, nodename(TargetName)); +process_args(["-sname", TargetName | Rest], Acc, _) -> + ThisNode = append_node_suffix(TargetName, "_maint_"), + {ok, _} = net_kernel:start([ThisNode, shortnames]), + process_args(Rest, Acc, nodename(TargetName)); +process_args([Arg | Rest], Acc, Opts) -> + process_args(Rest, [Arg | Acc], Opts). + +start_epmd() -> + [] = os:cmd("\"" ++ epmd_path() ++ "\" -daemon"), + ok. + +epmd_path() -> + ErtsBinDir = filename:dirname(escript:script_name()), + Name = "epmd", + case os:find_executable(Name, ErtsBinDir) of + false -> + case os:find_executable(Name) of + false -> + io:format("Could not find epmd.~n"), + halt(1); + GlobalEpmd -> + GlobalEpmd + end; + Epmd -> + Epmd + end. + +nodename(Name) -> + case re:split(Name, "@", [{return, list}, unicode]) of + [_Node, _Host] -> + list_to_atom(Name); + [Node] -> + [_, Host] = re:split(atom_to_list(node()), "@", [{return, list}, unicode]), + list_to_atom(lists:concat([Node, "@", Host])) + end. + +append_node_suffix(Name, Suffix) -> + case re:split(Name, "@", [{return, list}, unicode]) of + [Node, Host] -> + list_to_atom(lists:concat([Node, Suffix, os:getpid(), "@", Host])); + [Node] -> + list_to_atom(lists:concat([Node, Suffix, os:getpid()])) + end. + +%% For windows??? +create_mnesia_dir(DataDir, NodeName) -> + MnesiaDir = filename:join(DataDir, NodeName), + file:make_dir(MnesiaDir), + io:format("~s", [MnesiaDir]). + +mergeconf(EmqCfg, PluginsEtc, DestDir) -> + filelib:ensure_dir(DestDir),%% support brew on macosx + AppConf = filename:join(DestDir, appconf()), + case file:open(AppConf, [write]) of + {ok, DestFd} -> + CfgFiles = [EmqCfg | cfgfiles(PluginsEtc)], + lists:foreach(fun(CfgFile) -> + {ok, CfgData} = file:read_file(CfgFile), + file:write(DestFd, CfgData), + file:write(DestFd, <<"\n">>) + end, CfgFiles), + file:close(DestFd), + io:format("~s", [AppConf]), + halt(0); + {error, Error} -> + io:format(standard_error, ["Error opening file: ", file:format_error(Error), "\n"], []), + halt(1) + end. + +appconf() -> + {{Y, M, D}, {H, MM, S}} = calendar:local_time(), + lists:flatten( + io_lib:format( + "app.~4..0w.~2..0w.~2..0w.~2..0w.~2..0w.~2..0w.conf", [Y, M, D, H, MM, S])). + +cfgfiles(Dir) -> + [filename:join(Dir, CfgFile) || CfgFile <- filelib:wildcard("*.conf", Dir)]. + +chkconfig(File) -> + case file:consult(File) of + {ok, Terms} -> + case validate(Terms) of + ok -> + halt(0); + {error, Problems} -> + lists:foreach(fun print_issue/1, Problems), + %% halt(1) if any problems were errors + halt(case [x || {error, _} <- Problems] of + [] -> 0; + _ -> 1 + end) + end; + {error, {Line, Mod, Term}} -> + io:format(standard_error, ["Error on line ", file:format_error({Line, Mod, Term}), "\n"], []), + halt(1); + {error, Error} -> + io:format(standard_error, ["Error reading config file: ", file:format_error(Error), "\n"], []), + halt(1) + end. + +%% +%% Given a string or binary, parse it into a list of terms, ala file:consult/0 +%% +consult(Str) when is_list(Str) -> + consult([], Str, []); +consult(Bin) when is_binary(Bin)-> + consult([], binary_to_list(Bin), []). + +consult(Cont, Str, Acc) -> + case erl_scan:tokens(Cont, Str, 0) of + {done, Result, Remaining} -> + case Result of + {ok, Tokens, _} -> + {ok, Term} = erl_parse:parse_term(Tokens), + consult([], Remaining, [Term | Acc]); + {eof, _Other} -> + lists:reverse(Acc); + {error, Info, _} -> + {error, Info} + end; + {more, Cont1} -> + consult(Cont1, eof, Acc) + end. + +%% +%% Validation functions for checking the emqttd.config +%% +validate([Terms]) -> + Results = [ValidateFun(Terms) || ValidateFun <- get_validation_funs()], + Failures = [Res || Res <- Results, Res /= true], + case Failures of + [] -> + ok; + _ -> + {error, Failures} + end. + +%% Some initial and basic checks for the app.config file +get_validation_funs() -> + [ ]. + +print_issue({warning, Warning}) -> + io:format(standard_error, "Warning in emqttd.config: ~s~n", [Warning]); +print_issue({error, Error}) -> + io:format(standard_error, "Error in emqttd.config: ~s~n", [Error]). + +%% string:join/2 copy; string:join/2 is getting obsoleted +%% and replaced by lists:join/2, but lists:join/2 is too new +%% for version support (only appeared in 19.0) so it cannot be +%% used. Instead we just adopt join/2 locally and hope it works +%% for most unicode use cases anyway. +join([], Sep) when is_list(Sep) -> + []; +join([H|T], Sep) -> + H ++ lists:append([Sep ++ X || X <- T]). diff --git a/emqn2o.yaml b/emqn2o.yaml new file mode 100644 index 0000000000000000000000000000000000000000..a3c9108a1dbd10bcf4ff80da008dadc57e9fd1e7 --- /dev/null +++ b/emqn2o.yaml @@ -0,0 +1,127 @@ +--- + +apiVersion: rbac.authorization.k8s.io/v1beta1 +kind: ClusterRoleBinding +metadata: + name: emqtt-clusterrolebinding +subjects: +- kind: ServiceAccount + name: default + namespace: emqtt +roleRef: + kind: ClusterRole + name: cluster-admin + apiGroup: "" + +--- + +apiVersion: v1 +kind: Service +metadata: + name: ekka + namespace: emqtt + + labels: + app: ekka +spec: + type: NodePort + ports: + - port: 1883 + nodePort: 30001 + protocol: TCP + name: mqtt + - port: 8883 + nodePort: 30002 + protocol: TCP + name: mqttssl + - port: 8080 + nodePort: 30003 + protocol: TCP + name: mgmt + - port: 18083 + nodePort: 30004 + protocol: TCP + name: dashboard + - port: 4369 + nodePort: 30005 + protocol: TCP + name: mapping + - port: 8083 + nodePort: 30006 + protocol: TCP + name: ws + - port: 8084 + nodePort: 30007 + protocol: TCP + name: wss + selector: + app: ekka + +--- + +apiVersion: apps/v1beta1 +kind: StatefulSet +metadata: + name: ekka + namespace: emqtt +spec: + replicas: 1 + serviceName: ekka + template: + metadata: + labels: + app: ekka + spec: + securityContext: + fsGroup: 0 + runAsUser: 0 + containers: + - name: ekka + image: qomputer/one:v1 + imagePullPolicy: Always + volumeMounts: + - name: data-storage + mountPath: /home/nynja/server/Mnesia.ekka@127.0.0.1 + ports: + - containerPort: 1883 + - containerPort: 8883 + - containerPort: 8080 + - containerPort: 8083 + - containerPort: 8084 + - containerPort: 18083 + - containerPort: 8888 + - containerPort: 4369 + readinessProbe: + tcpSocket: + port: 1883 + initialDelaySeconds: 30 + periodSeconds: 40 + env: + - name: EMQ_NAME + value: "ekka" + - name: EMQ_CLUSTER__DISCOVERY + value: "k8s" + - name: EMQ_CLUSTER__K8S__APISERVER + value: "https://kubernetes.default:443" + - name: EMQ_CLUSTER__K8S__NAMESPACE + value: "emqtt" + - name: EMQ_CLUSTER__K8S__SERVICE_NAME + value: "ekka" + - name: EMQ_CLUSTER__K8S__ADDRESS_TYPE + value: "ip" + - name: EMQ_CLUSTER__K8S__APP_NAME + value: "ekka" + - name: EMQ_CLUSTER__NAME + value: "emqcl" + - name: EMQ_CLUSTER__AUTOHEAL + value: "on" + - name: EMQ_CLUSTER__AUTOCLEAN + value: "5m" + lifecycle: + preStop: + exec: + command: ["/bin/bash", "-c", "/home/nynja/server/halt_save.sh"] + volumes: + - name: data-storage + persistentVolumeClaim: + claimName: data-pv-claim diff --git a/etc/acl.conf b/etc/acl.conf index 608a9f1bacbc99f0a5269708b42f2adfe94f82a2..a56d51fc4e9d427fc7e2d4c4e1b3e1fc318168c5 100644 --- a/etc/acl.conf +++ b/etc/acl.conf @@ -1,5 +1,5 @@ {allow, {user, "dashboard"}, subscribe, ["$SYS/#"]}. -{allow, {ipaddr, "127.0.0.1"}, subscribe, ["$SYS/#", "#"]}. +{deny, {ipaddr, "127.0.0.1"}, subscribe, [{eq, "#"}]}. {allow, {ipaddr, "127.0.0.1"}, pubsub, ["$SYS/#", "#"]}. {allow, all, publish, ["events/#"]}. {deny, all, subscribe, ["$SYS/#", {eq, "#"}]}. diff --git a/etc/certs/cacert.pem b/etc/certs/cacert.pem index ca4948ed946773806dfff110fc0c4d956ae3ff4b..3f712aef51c403a8dd4e63c990ddcd2f9af7020a 100644 --- a/etc/certs/cacert.pem +++ b/etc/certs/cacert.pem @@ -1,17 +1,17 @@ ------BEGIN CERTIFICATE----- -MIICxjCCAa6gAwIBAgIJAPhU8tv3KMe/MA0GCSqGSIb3DQEBCwUAMBMxETAPBgNV -BAMMCE15VGVzdENBMB4XDTE2MTAzMTA3MTU0NVoXDTE3MTAzMTA3MTU0NVowEzER -MA8GA1UEAwwITXlUZXN0Q0EwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB -AQCtPcDnmjiVl7ScDhYvGaW+PUgfp7P5cM39mnrW6fkxhA0tgunWpWlYVKbcuh5y -4bTNYrOQpcFO3Zg62tva4XEL8O1huqTlGsAeysZ3vWE4/8NGN/3wZy0TKDvwiwOB -tbS3C5wcRQZohExL6yEL4XzDGk44x2mIs8/NzeG7Zycqybh9tsCJiHbLiTxnLa24 -v5USOtlvWye0hA0yUUqc2k7tKVmIMT4A4ulMb2sDVRrSLjyFDTI0c8grlPLfKbG8 -gpYLsHn9aAjqviyvmJdRLxwauqn+ghNWn1TyZwgAUxpoTtWeC0ilzEt18RP8vZjm -eCbEP4qQDDvSCdLrie5CezyxAgMBAAGjHTAbMAwGA1UdEwQFMAMBAf8wCwYDVR0P -BAQDAgEGMA0GCSqGSIb3DQEBCwUAA4IBAQBJ/I/QJjU+mgkIaaHImFcIYFrfBirC -vDiWo2W+zRh7CbcSf+jsksI99d230ixSDY36CPLKZeZhELST7xWKEELKbPdNbtOO -EM10+XteLSXKVNGXfrEbW973eum3FGLobMA9OcH6+qDaf08pibe7kuv10aAgSs/I -0Qg5H/UTAKQJKO9hhOgERM/FettuF+WGJaaZZZb9Y2YYBNRf/GtM8KHCjpCX9+XD -kdeQGO8Hn10H9tOmggyfdIpsunBcs2/6/exCp8RPBWurN2GSW2RcnS5xVL0r+SVW -VOhSDy1JwnNPczpqkqE74qAbAah0dTJFcFWzeGLVk7Kp+2pissAiU3gg ------END CERTIFICATE----- +-----BEGIN CERTIFICATE REQUEST----- +MIICtDCCAZwCAQAwbzELMAkGA1UEBhMCVUExDTALBgNVBAgMBEt5aXYxDTALBgNV +BAcMBEt5aXYxITAfBgNVBAoMGEludGVybmV0IFdpZGdpdHMgUHR5IEx0ZDEfMB0G +A1UEAwwWdHJhbnNsYXRlLmNpLm55bmphLm5ldDCCASIwDQYJKoZIhvcNAQEBBQAD +ggEPADCCAQoCggEBANLs7ZQ4qi8oQZ510QqR6Rgie8KFyauYYr1kp8VOzDEXHTm3 +ntbCa3qo9yoGdZZh38Nx/CeSUm5hf70dnYE7FYFvzZpqMq+7CpnxsMiEIzL1815k +PB/XGTAZzPvT0CGTjlekwBjTM/AkJV57+cgrbYu7KUJ+7L+UvOrodF1O43d1+uJE +z0sJintpQoLCyO0MYyyiAPozifaOI5c6UkNz0k73tdwbnjequAFvPm0gMmJ3FvCk +7Y0uEWaqR2yh9YXVaHqWMb4raJdwCQXevYgO8z7ptDnJYnQvPAy9M/WLFOygZjLf +KJVh3//tP9z/yFGjATkft2j6BmDJnsuKHf9W9B8CAwEAAaAAMA0GCSqGSIb3DQEB +CwUAA4IBAQCZP9jU1nqLrewCmRv7RBN8DTUOQiOzSy0+ldNXY1rBL5lo7gjgQYoT +47o1kFiJEbpFEXxHunfotNdoalfBLkA6MNhTmRbLOhUaocLrgSCmwWnteMhZ22yg +mgyTwppEdkH6l9gxuHz6XoKaDQ0FmVst6+C+otpQJyeuoBz8FtU8nuGeo1Bo0DKW +oo67GO7DHQGlytnMKXRHSCeyOUN/gwznRe6w4KRZBmAftPfNGvMXqxKl8/24g+aG +zK82YqO0MoGaKHaSuin+bv0Tdylc/wnEUP4rHkpZGGUbnzzOSB/JIoY3YbSwTcUi +HySRzB0B90JELF7eHDhYVkTn/2gFtG0V +-----END CERTIFICATE REQUEST----- diff --git a/etc/certs/cert.pem b/etc/certs/cert.pem index 58aa2c4ef1a112301faeebe19f6ef3aaae9a606e..276ba49ac5aba24091a07d9906e9832d8d777331 100644 --- a/etc/certs/cert.pem +++ b/etc/certs/cert.pem @@ -1,18 +1,20 @@ -----BEGIN CERTIFICATE----- -MIIC9jCCAd6gAwIBAgIBATANBgkqhkiG9w0BAQsFADATMREwDwYDVQQDDAhNeVRl -c3RDQTAeFw0xNjEwMzEwNzE1NDVaFw0xNzEwMzEwNzE1NDVaMDkxJjAkBgNVBAMT -HWRlbmdoYWlndWlkZU1hY0Jvb2stQWlyLmxvY2FsMQ8wDQYDVQQKEwZzZXJ2ZXIw -ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQC4Ena4vgWrzwUB0hGW1v0v -K986FhU5ZdYz5H5MGonfWwv89nR2DlftSDXEvKFyc2MT81GGm16VJv3mVpQJLuKA -xLBLY7a1zSrJdugXWy+mgJJTPW6KjTY4jPtfCl6x/yVr8YclVa8XO0JFzOme2LMV -Ylc/ixVEa66UpxRNrg5yWHS26KcB1lE3GLERoRBKF7nsyGqGY4X9TypBwglCVoqK -3dKVGwCvFur+oPnt/C5pwR6UmUV/Ppf1EaRD7Po+xcyJSeCvszG3FH4iHsDHnjLe -DR6lxouvMCb+aKJi9d0xowOjhbKoFMF179t4SVnptQeq+U6ui3cPKUjia7Zh1tZT -AgMBAAGjLzAtMAkGA1UdEwQCMAAwCwYDVR0PBAQDAgUgMBMGA1UdJQQMMAoGCCsG -AQUFBwMBMA0GCSqGSIb3DQEBCwUAA4IBAQB2jlDPiZfP/whsvvFn43g37QMwX5ST -Z5OpmEFnFjAH3ec0PPqPrKYEu00q5wEC+8L6uVH8FHOFf11JLH4wl11/C/mvE92D -qZtGG8KCnG2+rk5OJPGX+28Z+OnCZlXOjQ8qd2x5KtIW50JuXJ3cbDRHtF/TVanm -Exu+TCBeToNwbcU2sfQnbljkUTj4idUFz0pq3uvw3dA4R1J2foungPAYXSWcVhtb -RYtG8epIvkAyyUE5nY3kC05AUml6gSZkrJiYM5I1IJTX1lQ7Pv2yxRBZUtTx33rP -ccnsW6tbHTDBG8UDHx4LKHErdWFgCJWI81EUEcTip9g2zCOGTWKnpz+z +MIIDWjCCAkICCQDPVIdsxF6HMTANBgkqhkiG9w0BAQsFADBvMQswCQYDVQQGEwJV +QTENMAsGA1UECAwES3lpdjENMAsGA1UEBwwES3lpdjEhMB8GA1UECgwYSW50ZXJu +ZXQgV2lkZ2l0cyBQdHkgTHRkMR8wHQYDVQQDDBZ0cmFuc2xhdGUuY2kubnluamEu +bmV0MB4XDTE5MDExNTE0NTAyOVoXDTI5MDExMjE0NTAyOVowbzELMAkGA1UEBhMC +VUExDTALBgNVBAgMBEt5aXYxDTALBgNVBAcMBEt5aXYxITAfBgNVBAoMGEludGVy +bmV0IFdpZGdpdHMgUHR5IEx0ZDEfMB0GA1UEAwwWdHJhbnNsYXRlLmNpLm55bmph +Lm5ldDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBANLs7ZQ4qi8oQZ51 +0QqR6Rgie8KFyauYYr1kp8VOzDEXHTm3ntbCa3qo9yoGdZZh38Nx/CeSUm5hf70d +nYE7FYFvzZpqMq+7CpnxsMiEIzL1815kPB/XGTAZzPvT0CGTjlekwBjTM/AkJV57 ++cgrbYu7KUJ+7L+UvOrodF1O43d1+uJEz0sJintpQoLCyO0MYyyiAPozifaOI5c6 +UkNz0k73tdwbnjequAFvPm0gMmJ3FvCk7Y0uEWaqR2yh9YXVaHqWMb4raJdwCQXe +vYgO8z7ptDnJYnQvPAy9M/WLFOygZjLfKJVh3//tP9z/yFGjATkft2j6BmDJnsuK +Hf9W9B8CAwEAATANBgkqhkiG9w0BAQsFAAOCAQEAotACgSqQE1M7nhOOsrhMvi5r +nRydS9td7Tx9RyHGW0NpfFZavr14J652NzbO/By97kqB6JaTjedfAVl0+OS5+9o5 +oc9gxVtJa8p6MXTfoPC6dbX2KfjD1dwVwKkKQo4JNz/etvWfEwXj9pmNVBRBac9T +Ty6WCxiN5Qzh1QJ+I3Iy4AoF6zaHI/NtTNaoB2I3bH2XBHClCVXJreHQzpvG1l9r +8WM3xNz/OMYTe7Ahe5HYHijwD8k3I2uP/yrirOfFBvj+xlrhhvA99/Z1tBN1+qGY +fROPlVQmFIiyekz/gRIsP0qOn1cNUcP/pRi2isfY5QeKqarY7vQIL/eX+IPl2w== -----END CERTIFICATE----- diff --git a/etc/certs/key.pem b/etc/certs/key.pem index 7001093ef6fd3f14d516019ff805f825263131c1..1201956aa3e5af42b86f4453a801b1e7623f49b9 100644 --- a/etc/certs/key.pem +++ b/etc/certs/key.pem @@ -1,27 +1,27 @@ -----BEGIN RSA PRIVATE KEY----- -MIIEpAIBAAKCAQEAuBJ2uL4Fq88FAdIRltb9LyvfOhYVOWXWM+R+TBqJ31sL/PZ0 -dg5X7Ug1xLyhcnNjE/NRhptelSb95laUCS7igMSwS2O2tc0qyXboF1svpoCSUz1u -io02OIz7Xwpesf8la/GHJVWvFztCRczpntizFWJXP4sVRGuulKcUTa4Oclh0tuin -AdZRNxixEaEQShe57MhqhmOF/U8qQcIJQlaKit3SlRsArxbq/qD57fwuacEelJlF -fz6X9RGkQ+z6PsXMiUngr7MxtxR+Ih7Ax54y3g0epcaLrzAm/miiYvXdMaMDo4Wy -qBTBde/beElZ6bUHqvlOrot3DylI4mu2YdbWUwIDAQABAoIBADXYWNhT5c7LYTiW -HcUVIL0CxWr1eMHwk0dcyME0Zi5rMMePxKOgMIJdxDTHxSZ4sHvuimOo4XMaE92k -Z+uDxohKgROcmJ735FNIsD3c08SOCb/F0adABaNnQkUcAHVrIKRB4/m85doS4KEQ -fyqTU1enC8Svx8nbAhfEBEFw8BLsZD9UnQAEAU5W9S5aKPHNrYRDz5UE0ZP28ixC -4PtCew96uCqA0u+xZnWCGawF27FD9P88pcYSJqebF1iFYkXrAwdhAbqewHOqQJXf -KJpbpjflBvZr/oTVZ3GAnnHnZDiusFmCKIHB9dKimHMdTFVIU2ikOeJZLtgXsBjb -Wn3Fa8kCgYEA2fK0t9NPmELw43D7VoCNeUmu6KmLLd7CeRiQ/OkPLKTqrudnUZGi -uMinPFijGTLX3SmByAVOkzMKBQOYF+eB1X24kbRLmL4JKzr04hSqOKqG5gJctC+x -V5qQX7ZxrNxFRiSodILbnQN/z1gwZMfrAU0t0EKIKjZR3lpj8CELv1cCgYEA2DWn -9V6PCZPcHzoFabhb8DJFglUTHk0zINVe97qldvMvn0MgsjgyS2j954nX8ef7uE1O -Cf+9nN709Fu8kEC7/KzWXxP3/O58TfJ6NivCQSr5i0OJLumQMVNrS+u/VG1PaVbS -2oCwP3QFayOxZSj9wq2MARd1JkqzHmi8skZLz2UCgYEAgtnv3En3CLBwFe14SPgH -eGFfrPpVwGV0luXD7sQyQxiEehwecN+iNZTqqxWAXpmi9np8G83r3f6PrnD4+Kka -z0Wa8Yewt3So5paP/chwZnMjaKbUZ64WqET5Fy3fU+wvfyx1IvaJydwW+TK2Y1uP -4Yknz1iSjd1tC7VzOPFuLyMCgYBrTFWKQ98glayMIrNFACVAUvKD98yBITbaeImk -z5AGNDHSC/JR/+mV2wkGuzXb65DUqiisdaqYC13tVwmBXV7tyqiojrRnZcNyu39D -GvxQcw9cuat/CJJyqD97cgeF0qmyUVBa97qAAwgdX51N4sXss0vjzsxosHGsCbZ7 -kr9UsQKBgQCMTtdCeA+uK/OeJtzf4CYZKR9xllQ+P6gCtbQ7WHuLBX/x+ZhvTC0p -qVLVWwFsJ6ivc1f74sy8hZPiePk9fqAqA1JIjDHrof0M3TxRVFvB7dej5XIYVirn -521DyZGfE+N7HA7qW5cGKZT0+UYLVp4gnv88nNKDuS18lafy8JRrfQ== +MIIEpgIBAAKCAQEA0uztlDiqLyhBnnXRCpHpGCJ7woXJq5hivWSnxU7MMRcdObee +1sJreqj3KgZ1lmHfw3H8J5JSbmF/vR2dgTsVgW/Nmmoyr7sKmfGwyIQjMvXzXmQ8 +H9cZMBnM+9PQIZOOV6TAGNMz8CQlXnv5yCtti7spQn7sv5S86uh0XU7jd3X64kTP +SwmKe2lCgsLI7QxjLKIA+jOJ9o4jlzpSQ3PSTve13BueN6q4AW8+bSAyYncW8KTt +jS4RZqpHbKH1hdVoepYxvitol3AJBd69iA7zPum0OclidC88DL0z9YsU7KBmMt8o +lWHf/+0/3P/IUaMBOR+3aPoGYMmey4od/1b0HwIDAQABAoIBAQCfSgh9mHGFnXw4 +xDbzKZAEBv65v7m2miVvbFV7H2/7Xr5WeUI+Y6Mxherq1Of2gsGu4yUPaWRRMdZ7 +JqNtFga+soVuKOdQaQuviKP+OjQu5yk8zqvNS+8csU9pmuOih8HGlIfx94ZQw6to ++G2BarKXBOg67Im0gQpOyPAgC1SjQJoHkZAzjQkgALDjdBIw63+F9QNTwSnWGVK2 +lmyZkPdU6qObV6x4n0OLiI6PCQKWQtXckcxjbLE/GiMB6L/9tG45WEQEbLjAKhKL ++rtUAHHqrcJWr5Y0tZzYfn11kYJKj6GoQOqCRkPHMORO3sctD7akM/A/i0hyZSPN +ycnk5GYJAoGBAOyb0vuhY0LEXlgfa9txhC3nNcu+cg8ftGVKTsNGBtMytO+52YGu +t8PysQnoyFMhT/ZcgGWlMchFy01za7J/kfhZKCE/kJMLURhS8RxwHQqhr6mSOqWk +9gGOKTq+B/SRyiCcXBbF53EYfNg76VCDuY2cEdBpAyAK357zJgN5Be91AoGBAOQ2 +QclKlXiqt7ehS5y4UKtR0s1PThr2Hf/9tBFLWITyt0PgSuUu3KN3erhAQV+3Tg3y +B5IQUI09gkUk+jCD3SM/m9AYD/fx2SuqIjX2kX1M/PsT9k5YWe0ECn10krjX6uSs +H+uN1fw70vk8IvratYmLjDi3hYUMdlZVi7NKF5bDAoGBAMLskME6sNKlge/pWur7 +NuC2NQx4BfFNtkkoEYEwJJlDkepY2sbKkJv+STmXq8lIjV6x0BltIXkTfqx85QG1 +tSS7FqNlgY6SaSm30ukRt+s1TslNLcGfCAg5/N9nBJjIrpN1HSypt8TlXpyppsns +6yWtqMNgb/Gq45VITU3ZGzmtAoGBAM7SJTjJ8UCUZz9yXFZB7En3M7aBwuVIW9Pe +F0z9HdIPLevD39xoZel3pK5K/XRDaefViurjAj2emusTIfLJ7qW9RfaHeCFa+1hJ +Lgv908jc9wCNDNHf3E+jM+aSuU3v0J9BJwSRXHbluMPLrl/tykxHPeNA3y6/JpgD +m0H3Sf2FAoGBAIUXuSdXmfL2Ag6Cx8Bk95Y25yRtuuSpBcAl98JgQA4+FzuITEQO +VRK5Fd7Skfy4ZbpNgUJ+hUO5/o/gksv14fLphKH5n36Z+90fedwzuMjhLaRit1WL +Cjk3cdsi2KGQiQn2vWBO3CCZvTklu/8FIfj4Qtmusaddt1R3kudbzymb -----END RSA PRIVATE KEY----- diff --git a/etc/emq.conf b/etc/emq.conf index afbfe7c01d1aad058f5d46be8fcc59ed5b813f88..fc40c1a5b167fef91fec3848ab560350471f2ac7 100644 --- a/etc/emq.conf +++ b/etc/emq.conf @@ -1,10 +1,10 @@ -node.name = emqttd@127.0.0.1 +node.name = md@127.0.0.1 node.cookie = emq_dist_cookie node.smp = auto node.kernel_poll = on node.async_threads = 32 -node.process_limit = 256000 -node.max_ports = 65536 +node.process_limit = 2097152 +node.max_ports = 1048576 node.dist_buffer_size = 32MB node.max_ets_tables = 256000 node.fullsweep_after = 1000 @@ -48,8 +48,8 @@ mqtt.bridge.ping_down_interval = 1 mqtt.plugins.etc_dir ={{ platform_etc_dir }}/plugins/ mqtt.plugins.loaded_file = {{ platform_data_dir }}/loaded_plugins mqtt.listener.tcp = 1883 -mqtt.listener.tcp.acceptors = 8 -mqtt.listener.tcp.max_clients = 1024 +mqtt.listener.tcp.acceptors = 16 +mqtt.listener.tcp.max_clients = 1000000 mqtt.listener.tcp.backlog = 1024 mqtt.listener.ssl = 8883 mqtt.listener.ssl.acceptors = 4 diff --git a/etc/emq.conf.dist b/etc/emq.conf.dist new file mode 100644 index 0000000000000000000000000000000000000000..5f1b45e1cfd4251acc69dcce1b527a41d9313190 --- /dev/null +++ b/etc/emq.conf.dist @@ -0,0 +1,75 @@ +node.name = ${ERL_NODE_NAME} +node.cookie = ${ERL_CLUSTER_COOKIE} +node.smp = auto +node.kernel_poll = on +node.async_threads = 32 +node.process_limit = 2097152 +node.max_ports = 1048576 +node.dist_buffer_size = 32MB +node.max_ets_tables = 256000 +node.fullsweep_after = 1000 +node.crash_dump = {{ platform_log_dir }}/crash.dump +node.dist_net_ticktime = 60 +node.heartbeat = on +log.dir = {{ platform_log_dir }} +log.console = console +log.syslog = on +log.syslog.level = error +log.console.level = error +log.error.file = {{ platform_log_dir }}/error.log +log.crash = on +log.crash.file = {{ platform_log_dir }}/crash.log +mqtt.acl_nomatch = allow +mqtt.allow_anonymous = true +mqtt.acl_file = {{ platform_etc_dir }}/acl.conf +mqtt.cache_acl = true +mqtt.max_clientid_len = 1024 +mqtt.max_packet_size = 64KB +mqtt.client.idle_timeout = 30s +mqtt.client.enable_stats = off +mqtt.session.upgrade_qos = off +mqtt.session.max_inflight = 32 +mqtt.session.retry_interval = 20s +mqtt.session.max_awaiting_rel = 100 +mqtt.session.await_rel_timeout = 20s +mqtt.session.enable_stats = off +mqtt.session.expiry_interval = 2h +mqtt.queue.type = simple +mqtt.queue.priority = topic/1=10,topic/2=8 +mqtt.queue.max_length = infinity +mqtt.queue.low_watermark = 20% +mqtt.queue.high_watermark = 60% +mqtt.queue.qos0 = true +mqtt.broker.sys_interval = 60 +mqtt.pubsub.pool_size = 8 +mqtt.pubsub.by_clientid = true +mqtt.pubsub.async = true +mqtt.bridge.max_queue_len = 10000 +mqtt.bridge.ping_down_interval = 1 +mqtt.plugins.etc_dir ={{ platform_etc_dir }}/plugins/ +mqtt.plugins.loaded_file = {{ platform_data_dir }}/loaded_plugins +mqtt.listener.tcp = 1883 +mqtt.listener.tcp.acceptors = 64 +mqtt.listener.tcp.max_clients = 1024000 +mqtt.listener.tcp.backlog = 1024 +mqtt.listener.ssl = 8883 +mqtt.listener.ssl.acceptors = 4 +mqtt.listener.ssl.max_clients = 512 +mqtt.listener.ssl.tls_versions = tlsv1.2,tlsv1.1,tlsv1 +mqtt.listener.ssl.handshake_timeout = 15s +mqtt.listener.ssl.keyfile = {{ platform_etc_dir }}/certs/key.pem +mqtt.listener.ssl.certfile = {{ platform_etc_dir }}/certs/cert.pem +mqtt.listener.http = 8083 +mqtt.listener.http.acceptors = 4 +mqtt.listener.http.max_clients = 64 +mqtt.listener.https = 8084 +mqtt.listener.https.acceptors = 4 +mqtt.listener.https.max_clients = 64 +mqtt.listener.https.handshake_timeout = 15 +mqtt.listener.https.keyfile = {{ platform_etc_dir }}/certs/key.pem +mqtt.listener.https.certfile = {{ platform_etc_dir }}/certs/cert.pem +sysmon.long_gc = false +sysmon.long_schedule = 240 +sysmon.large_heap = 8MB +sysmon.busy_port = false +sysmon.busy_dist_port = true diff --git a/etc/plugins/emq_dashboard.conf b/etc/plugins/emq_dashboard.conf index d88e7569b5071dcdc4f30bc852d28a265d1dc785..4e34f9df3f2bef4aa11f07199d1cefe4526fc39a 100644 --- a/etc/plugins/emq_dashboard.conf +++ b/etc/plugins/emq_dashboard.conf @@ -1,7 +1,6 @@ dashboard.listener.http = 18083 dashboard.listener.http.acceptors = 2 dashboard.listener.http.max_clients = 512 - dashboard.listener.https = 18084 dashboard.listener.https.acceptors = 2 dashboard.listener.https.max_clients = 512 diff --git a/halt_save.sh b/halt_save.sh new file mode 100755 index 0000000000000000000000000000000000000000..188aea85bd0c327bf7dc50cae8c40a9faefa906c --- /dev/null +++ b/halt_save.sh @@ -0,0 +1,15 @@ +#!/bin/sh + + +CLUSTER_COOKIE=emq_dist_cookie +NODE0="ekka-0" +#LOCAL_IP=$(hostname -i |grep -E -oh '((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])'|head -n 1) + +#if [ "$(hostname)" = "$NODE0" ]; then +# escript bin/nodetool -name ekka@127.0.0.1 -setcookie "777" "halt" +# sleep 7 +#fi + +#find $DB -type f ! -name "schema.DAT" -exec cp -t $DATA "{}" \+ + +escript bin/nodetool -name mq@127.0.0.1 -setcookie $CLUSTER_COOKIE "stop" diff --git a/k8s-start.sh b/k8s-start.sh new file mode 100755 index 0000000000000000000000000000000000000000..3b808c3a2a44a1eb99aeb4edee913e86eeebbe3e --- /dev/null +++ b/k8s-start.sh @@ -0,0 +1,121 @@ +#!/bin/sh + +## Shell setting +#if [ ! -z "$DEBUG" ]; then +# set -ex +#fi + +## Local IP address setting + +LOCAL_IP=$(hostname -i |grep -E -oh '((25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])\.){3,3}(25[0-5]|(2[0-4]|1{0,1}[0-9]){0,1}[0-9])'|head -n 1) + +## + +export ERL_CLUSTER_COOKIE=emq_dist_cookie +export ERL_NODE_NAME=mq@127.0.0.1 +envsubst < vm.args.dist > vm.args +envsubst < etc/emq.conf.dist > etc/emq.conf +#envsubst < etc/acl.conf.dist > etc/acl.conf + +sleep 3 + +## Nynja Base settings and plugins setting +# Base settings in ~server/etc/emq.conf +# Plugin settings in ~server/etc/plugins + +_EMQ_HOME="/home/nynja/server" + +if [ -z "$PLATFORM_ETC_DIR" ]; then + export PLATFORM_ETC_DIR="$_EMQ_HOME/etc" +fi + +if [ -z "$PLATFORM_LOG_DIR" ]; then + export PLATFORM_LOG_DIR="$_EMQ_HOME/log" +fi + +#if [ -z "$EMQ_NAME" ]; then +# export EMQ_NAME="$(hostname)" +#fi + +#if [ -z "$EMQ_HOST" ]; then +# export EMQ_HOST="$LOCAL_IP" +#fi + +if [ -z "$EMQ_WAIT_TIME" ]; then + export EMQ_WAIT_TIME=600 +fi + +#if [ -z "$EMQ_NODE_NAME" ]; then +# export EMQ_NODE_NAME="$EMQ_NAME@$EMQ_HOST" +#fi + +make start & +#tail -f /home/nynja/server/log/erlang.log.1 & + +# Sleep 5 seconds to wait for the loaded plugins catch up. +sleep 5 + +echo "['$(date -u +"%Y-%m-%dT%H:%M:%SZ")']: nynja start" + +# Wait and ensure emqttd status is running +WAIT_TIME=0 +while [ -z "$(/home/nynja/server/bin/emqttd_ctl status |grep 'is running'|awk '{print $1}')" ] +do + sleep 1 + echo "['$(date -u +"%Y-%m-%dT%H:%M:%SZ")']:waiting emqttd" + WAIT_TIME=$((WAIT_TIME+1)) + if [ $WAIT_TIME -gt $EMQ_WAIT_TIME ]; then + echo "['$(date -u +"%Y-%m-%dT%H:%M:%SZ")']:timeout error" + exit 1 + fi +done + +# Sleep 5 seconds to wait for the loaded plugins catch up. +sleep 5 + +echo "['$(date -u +"%Y-%m-%dT%H:%M:%SZ")']:emqttd start" + +# Run cluster script + +#if [ -x "./cluster.sh" ]; then +# ./cluster.sh & +#fi + +# Join an exist cluster + +if [ ! -z "$EMQ_JOIN_CLUSTER" ]; then + echo "['$(date -u +"%Y-%m-%dT%H:%M:%SZ")']:emqttd try join $EMQ_JOIN_CLUSTER" + /home/nynja/server/bin/emqttd_ctl cluster join $EMQ_JOIN_CLUSTER & +fi + +# Change admin password + +#if [ ! -z "$EMQ_ADMIN_PASSWORD" ]; then +# echo "['$(date -u +"%Y-%m-%dT%H:%M:%SZ")']:admin password changed to $EMQ_ADMIN_PASSWORD" +# /home/nynja/server/bin/emqttd_ctl admins passwd admin $EMQ_ADMIN_PASSWORD & +#fi + +# monitor emqttd is running, or the docker must stop to let docker PaaS know +# warning: never use infinite loops such as `` while true; do sleep 1000; done`` here +# you must let user know emqtt crashed and stop this container, +# and docker dispatching system can known and restart this container. +IDLE_TIME=0 +while [ $IDLE_TIME -lt 5 ] +do + IDLE_TIME=$((IDLE_TIME+1)) + if [ ! -z "$(/home/nynja/server/bin/emqttd_ctl status |grep 'is running'|awk '{print $1}')" ]; then + IDLE_TIME=0 + else + echo "['$(date -u +"%Y-%m-%dT%H:%M:%SZ")']:emqttd not running, waiting for recovery in $((25-IDLE_TIME*5)) seconds" + fi + sleep 5 +done + +# If running to here (the result 5 times not is running, thus in 25s emq is not running), exit docker image +# Then the high level PaaS, e.g. docker swarm mode, will know and alert, rebanlance this service + +# tail $(ls /opt/emqttd/log/*) + +echo "['$(date -u +"%Y-%m-%dT%H:%M:%SZ")']:emqttd exit abnormally" +exit 1 + diff --git a/priv/protobuf/service_room/Link.proto b/priv/protobuf/service_room/Link.proto index 1461e24295d4d0da15d195c6b60044d787d23821..418c0ec15b2d088fef8f530eea2c6b7c34638d09 100644 --- a/priv/protobuf/service_room/Link.proto +++ b/priv/protobuf/service_room/Link.proto @@ -8,12 +8,12 @@ option java_generic_services = true; option java_multiple_files = true; option java_package = "Link.grpc"; option java_outer_classname = "LinkCls"; -import public "linkStatus.proto"; message Link { - string id = 1; + string entity_id = 1; string name = 2; - string room_id = 3; - int64 created = 4; - linkStatus links_status = 5; + oneof type { + } + oneof status { + } } diff --git a/priv/protobuf/service_room/Room.proto b/priv/protobuf/service_room/Room.proto index a28c8eba1846b42da6d4b8fad8610c3ad8ca1927..13d1f556a2853b23fdd041862cb6917accca0740 100644 --- a/priv/protobuf/service_room/Room.proto +++ b/priv/protobuf/service_room/Room.proto @@ -4,34 +4,37 @@ syntax = "proto3"; package service_room; -import "google/protobuf/Any.proto"; 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 "roomType.proto"; +import public "Message.proto"; import public "Desc.proto"; -import public "roomStatus.proto"; message Room { string id = 1; string name = 2; - repeated google.protobuf.Any links = 3; + string links = 3; string description = 4; repeated Feature settings = 5; repeated Member members = 6; repeated Member admins = 7; repeated Desc data = 8; - roomType type = 9; + oneof type { + } string tos = 10; int64 tos_update = 11; int64 unread = 12; repeated int64 mentions = 13; repeated int64 readers = 14; - int64 last_msg = 15; + oneof last_msg { + int64 last_msg150 = 150; + Message last_msg151 = 151; + } int64 update = 16; int64 created = 17; - roomStatus status = 18; + oneof status { + } } diff --git a/pv.yaml b/pv.yaml new file mode 100644 index 0000000000000000000000000000000000000000..63e5d0f84cb104b5e158a88f0541dc0af213bd55 --- /dev/null +++ b/pv.yaml @@ -0,0 +1,26 @@ +apiVersion: v1 +kind: PersistentVolume +metadata: + name: pv0001 +spec: + accessModes: + - ReadWriteOnce + capacity: + storage: 10Gi + storageClassName: "standart" + hostPath: + path: /home/nynja/server/Mnesia.ekka@127.0.0.1/ + type: DirectoryOrCreate +--- +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: data-pv-claim +spec: + volumeName: pv0001 + storageClassName: "standart" + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 5Gi diff --git a/rebar.config b/rebar.config index 91e3b037d0ad25e04846b62be9d024c337db9408..e21783f8f3255550aa488ee2080a7040fd4733be 100644 --- a/rebar.config +++ b/rebar.config @@ -2,26 +2,22 @@ {lib_dirs,["apps","deps"]}. {deps_dir,"deps"}. {deps, [ - {lager, ".*", {git, "https://github.com/erlang-lager/lager.git","3.6.1"}}, - - {bert, ".*", {git, "git://github.com/synrc/bert",{tag,"1.11.1"}}}, - {active, ".*", {git, "git://github.com/synrc/active",{tag,"4.11"}}}, - {bpe, ".*", {git, "git://github.com/spawnproc/bpe.git",{tag,"3.11"}}}, - {n2o, ".*", {git, "git://github.com/synrc/mqtt", {tag,"5.10"}}}, - {esockd, ".*", {git, "https://github.com/voxoz/esockd",{tag,"master"}}}, - {emqttc, ".*", {git, "https://github.com/voxoz/emqttc",{tag,"master"}}}, - {emqttd, ".*", {git, "git://github.com/synrc/emqttd",{tag,"5.10"}}}, + {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/synrc/emqttd",{tag,"master"}}}, + {n2o, ".*", {git, "git://github.com/synrc/n2o", {tag,"6.4"}}}, + {emqttc, ".*", {git, "git://github.com/voxoz/emqttc",{tag,"master"}}}, {rest, ".*", {git, "git://github.com/synrc/rest",{tag,"5.10"}}}, - {review, ".*", {git, "git://github.com/synrc/review",{tag,"1.10"}}}, - {gen_smtp, ".*", {git, "git://github.com/ne-luboff/gen_smtp",{tag,"master"}}}, - {emq_dashboard, ".*", {git, "git://github.com/ne-luboff/emq_dashboard",{tag,"master"}}}, + {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/cryoflamer/libphonenumber_erlang.git",{tag,"master"}}}, - + {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/evanmiller/erlydtl",{tag,"master"}}}, + {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.0"}}}, + {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"}}}, @@ -31,6 +27,21 @@ {rfc3339, ".*", {git, "https://github.com/talentdeficit/rfc3339",{tag,"0.2.2"}}}, {locus, ".*", {git, "https://github.com/g-andrade/locus.git",{tag,"master"}}}, {prometheus, ".*", {git, "https://github.com/deadtrickster/prometheus.erl",{tag,"master"}}}, - {cowboy, ".*", {git, "git://github.com/voxoz/cowboy", {tag,"master"}}} + {cowboy, ".*", {git, "git://github.com/voxoz/cowboy", {tag,"master"}}}, + {'erlang-uuid', ".*", {git, "https://github.com/avtobiff/erlang-uuid.git",{tag,"master"}}} ]}. -{erl_opts, [{parse_transform,lager_transform},{parse_transform, oc_transform},debug_info]}. \ No newline at end of file +{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}]} + ]} + ]}. diff --git a/rebar.lock b/rebar.lock new file mode 100644 index 0000000000000000000000000000000000000000..f641a2fbb6bca33e10d4417842103b9f24c3d93d --- /dev/null +++ b/rebar.lock @@ -0,0 +1,164 @@ +{"1.1.0", +[{<<"base64url">>, + {git,"https://github.com/dvv/base64url.git", + {ref,"f2c64ed8b9bebc536fad37ad97243452b674b837"}}, + 0}, + {<<"bert">>, + {git,"git://github.com/synrc/bert", + {ref,"6cde41b32448e85ecd48225221d6f978943a1bad"}}, + 0}, + {<<"bpe">>, + {git,"git://github.com/synrc/bpe", + {ref,"356c9e621c38e927a8611ecac592bcdc8d689026"}}, + 0}, + {<<"certifi">>,{pkg,<<"certifi">>,<<"2.4.2">>},1}, + {<<"counters">>, + {git,"https://github.com/deadtrickster/counters.erl.git", + {ref,"c3f4aa3acdf71c3db0a4b3fc1343aa45de2c5df0"}}, + 0}, + {<<"cowboy">>, + {git,"git://github.com/voxoz/cowboy", + {ref,"c1cfbfa5dc6b5f6ecd9591ad5bf642b6a107a7f5"}}, + 0}, + {<<"cowlib">>, + {git,"git://github.com/voxoz/cowlib", + {ref,"5cc0038d0a3ae6a829646ddfff998d8c491969ca"}}, + 1}, + {<<"ctx">>, + {git,"https://github.com/tsloughter/ctx.git", + {ref,"a5a6b0948708e02bc7b9cb6248807c9ff8327940"}}, + 0}, + {<<"emq_dashboard">>, + {git,"https://github.com/synrc/emq_dashboard", + {ref,"f711e8d2b0a992f5540123f3504eebabc324a684"}}, + 0}, + {<<"emqttc">>, + {git,"git://github.com/voxoz/emqttc", + {ref,"141fd2e925be854321d22005313762adb1195f48"}}, + 0}, + {<<"emqttd">>, + {git,"git://github.com/synrc/emqttd", + {ref,"c3d0b7b092fffe7bebe27af27dbbfe1714757d1c"}}, + 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}, + {<<"erlydtl">>, + {git,"git://github.com/voxoz/erlydtl", + {ref,"bdebe6f87d8f989018facbbf8dc6320936ffe98f"}}, + 0}, + {<<"esockd">>, + {git,"https://github.com/voxoz/esockd", + {ref,"a80634b961c315ffe5f020d73236473b53ae5dc9"}}, + 0}, + {<<"forms">>, + {git,"git://github.com/synrc/forms", + {ref,"845feb45a46dfc2e0e9a156da9c01c218d8fd6cc"}}, + 1}, + {<<"gen_logger">>, + {git,"git://github.com/voxoz/gen_logger", + {ref,"5b14530363feb0b049c4f5c7c606f815aec781d2"}}, + 1}, + {<<"gen_smtp">>, + {git,"git://github.com/voxoz/gen_smtp", + {ref,"89eb26f8a81b0dce76aedae5695a923c8ac98695"}}, + 0}, + {<<"goldrush">>, + {git,"git://github.com/voxoz/goldrush.git", + {ref,"9e92405985827fabd2508b92bdc8b28b9e17b668"}}, + 2}, + {<<"gproc">>, + {git,"https://github.com/uwiger/gproc", + {ref,"1d16f5e6d7cf616eec4395f2385e3a680a4ffc9f"}}, + 0}, + {<<"ibrowse">>, + {git,"git://github.com/cmullaparthi/ibrowse.git", + {ref,"c97136cfb61fcc6f39d4e7da47372a64f7fca04e"}}, + 1}, + {<<"jsx">>, + {git,"https://github.com/talentdeficit/jsx.git", + {ref,"fc2a001073f2300ba38427c23e83d5673c020542"}}, + 0}, + {<<"jwt">>, + {git,"https://github.com/artemeff/jwt.git", + {ref,"6fd754fec1cd2e4577a5d7121233371900aa44e7"}}, + 0}, + {<<"kvs">>, + {git,"git://github.com/synrc/kvs", + {ref,"c0f9bd4766c2751e8f7b1752f8eb949d17f01b87"}}, + 1}, + {<<"lager">>, + {git,"git://github.com/voxoz/lager", + {ref,"1ecf4c17e9e39dd7a1943140477f632d17518f0c"}}, + 1}, + {<<"libphonenumber_erlang">>, + {git,"https://github.com/marinakr/libphonenumber_erlang.git", + {ref,"3a6be75ef4f6fdd40fa8afee2a7fc5cf2ddb8601"}}, + 0}, + {<<"locus">>, + {git,"https://github.com/g-andrade/locus.git", + {ref,"0ea9079ce5686573e0e70e2b1311343dd25feef8"}}, + 0}, + {<<"migresia">>, + {git,"https://github.com/yoonka/migresia.git", + {ref,"40e11825e01502d045e87bf8b5d7dc5a9d6e3f73"}}, + 0}, + {<<"mini_s3">>, + {git,"https://github.com/chef/mini_s3.git", + {ref,"df0c68ea901343b8e0c647142d88d7f3aae27e7b"}}, + 0}, + {<<"mochiweb">>, + {git,"git://github.com/voxoz/mochiweb", + {ref,"c53540557dba6d79c347209fc9980e97dcf3dd3f"}}, + 1}, + {<<"n2o">>, + {git,"git://github.com/synrc/n2o", + {ref,"f59f997d1432515403f505b382ce6717454abaa3"}}, + 0}, + {<<"nitro">>, + {git,"git://github.com/synrc/nitro", + {ref,"1aeb421c332f94b135563f8e855b139c1b067f59"}}, + 1}, + {<<"opencensus-erlang">>, + {git,"https://github.com/voxoz/opencensus-erlang", + {ref,"8dc9ae86f5c1ef69593fe37b49e649afed6bd201"}}, + 0}, + {<<"parse_trans">>,{pkg,<<"parse_trans">>,<<"3.3.0">>},2}, + {<<"prometheus">>, + {git,"https://github.com/deadtrickster/prometheus.erl", + {ref,"46ea4b487baf4f6cc44495eae07582808e0369d4"}}, + 0}, + {<<"ranch">>, + {git,"git://github.com/voxoz/ranch", + {ref,"1a75038c82ede22efec46a7dca192b8ce26309e0"}}, + 1}, + {<<"rest">>, + {git,"git://github.com/synrc/rest", + {ref,"9a4e21d50a96b06649944c9a88ad595114db6c31"}}, + 0}, + {<<"rfc3339">>, + {git,"https://github.com/talentdeficit/rfc3339", + {ref,"90effc078c5e673d025b2c1269a153ad4748d3df"}}, + 0}, + {<<"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}, + {<<"wts">>, + {git,"https://github.com/tsloughter/wts.git", + {ref,"5613b6c4354867fd2b02fde5ef15bf80190b8586"}}, + 0}]}. +[ +{pkg_hash,[ + {<<"certifi">>, <<"75424FF0F3BAACCFD34B1214184B6EF616D89E420B258BB0A5EA7D7BC628F7F0">>}, + {<<"parse_trans">>, <<"09765507A3C7590A784615CFD421D101AEC25098D50B89D7AA1D66646BC571C1">>}, + {<<"ssl_verify_fun">>, <<"F0EAFFF810D2041E93F915EF59899C923F4568F4585904D010387ED74988E77B">>}, + {<<"stacktrace_compat">>, <<"8AD31C32C9A0EADB1EB298F04DC8B0C8D79BCC6233A638B02791FFCA4F331275">>}]} +]. diff --git a/sys.config b/sys.config index 718184dcf54a6db71a8c17b84b3bbb68d3e087bf..b8b352c6ae2055ed81723eb64f3c635b3b671335 100644 --- a/sys.config +++ b/sys.config @@ -5,6 +5,7 @@ {google,"apps/service/priv/"}, {disallowed,['feed', 'Whitelist', 'FakeNumbers']}, {allowed_hrl, ["roster"]}, + {custom_validate, {roster, validate}}, {swift,"apps/roster/priv/macbert/"}]}, {roster,[ {upload,"./storage"}, @@ -22,8 +23,8 @@ {customer_id,<<"CF6E2918-89A9-417F-8074-382908FEDCD9">>}, {api_key,<<"JE1zC3gjYg1fITkPK4xYtAGk0uVpbsWSqi7e54wg+sLbMGAbKoJSw0/BRJaAezZpVZzEQfHYyL3PKutGfZ38Gg==">>}]}, {amazon_api,[ - {access_key_id,<<"AKIAIVFYOPZSACBLBBSA">>}, - {secret_access_key,<<"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">>}, + {access_key_id,<<"AKIAITNYSJXI2NUDRRXA">>}, + {secret_access_key,<<"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">>}, {sts, [ {host, <<"sts.amazonaws.com">>}, {region, <<"us-east-1">>}, @@ -48,9 +49,10 @@ {job_delay, 60}, %% 1 mins {auth_ttl, 900}, %% 15 mins {auth_check_ip, false}, - {get_sessions_api, false}, + {get_sessions_api, true}, {whitelist_check, false}, {fake_numbers_check, true} + % {validate_token, micro} ]}, {rest, [{port,8888}, {basic_auth,[ @@ -67,14 +69,24 @@ {formatter,n2o_bert}, {auth_ttl, 900}, %% 15 mins {protocols,[n2o_nitro,n2o_ftp,roster_proto,kvs_stream]}, - {log_modules,roster}, + {log_modules,[roster]}, {log_level,roster}, - {vnode, {roster, get_vnode}}]}, + {vnode, {roster, get_vnode}}, + {validate, {roster, validate}} + ]}, {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">>} @@ -120,12 +132,13 @@ {ssl,8883, [{sslopts, [{versions,['tlsv1.2','tlsv1.1',tlsv1]}, - {keyfile,"etc/certs/key.pem"}, - {certfile,"etc/certs/cert.pem"}]}, + {cacertfile,"etc/certs/cert.pem"}, + {keyfile,"etc/certs/privkey.pem"}, + {certfile,"etc/certs/fullchain.pem"}]}, {connopts,[]}, {sockopts,[{nodelay,true}]}, - {acceptors,4}, - {max_clients,512}]}, + {acceptors,8}, + {max_clients,1048576}]}, {http,8083, [{connopts,[]}, {sockopts,[{nodelay,true}]}, @@ -133,12 +146,13 @@ {max_clients,1048576}]}, {https,8084, [{sslopts, - [{keyfile,"etc/certs/key.pem"}, - {certfile,"etc/certs/cert.pem"}]}, + [{cacertfile,"etc/certs/cert.pem"}, + {keyfile,"etc/certs/privkey.pem"}, + {certfile,"etc/certs/fullchain.pem"}]}, {connopts,[]}, {sockopts,[{nodelay,true}]}, - {acceptors,4}, - {max_clients,64}]}]}, + {acceptors,8}, + {max_clients,1048576}]}]}, {sysmon, [{long_gc,false}, {long_schedule,240}, @@ -146,7 +160,7 @@ {busy_port,false}, {busy_dist_port,true}]}]}, {kvs, [{dba,store_mnesia}, - {schema, [kvs_user, kvs_acl, kvs_feed, kvs_subscription, roster, emqttd_kvs, bpe_metainfo]} + {schema, [kvs_user, kvs_acl, kvs_feed, kvs_subscription, roster, micro, emqttd_kvs, bpe_metainfo]} %% ,{generation, {roster_test, limit}} %% ,{forbidding, {roster_test, forbid}} ]}, diff --git a/vm.args b/vm.args index f981b06c6d53a2206b5a667ade1c4138681bc92f..2701e0ee5c164561be3244ffc4c7881bcc87b35d 100644 --- a/vm.args +++ b/vm.args @@ -3,9 +3,9 @@ -env ERL_CRASH_DUMP log/crash.dump +e 256000 -env ERL_FULLSWEEP_AFTER 1000 --mnesia dc_dump_limit 10 --mnesia dump_log_write_threshold 700 --mnesia dump_log_time_threshold 400000 +-mnesia dc_dump_limit 40 +-mnesia dump_log_write_threshold 50000 +-mnesia dump_log_time_threshold 500000 +Q 134217727 +P 2560000 +A 32 diff --git a/vm.args.dist b/vm.args.dist new file mode 100644 index 0000000000000000000000000000000000000000..cd2012f84c2074f19a56a98aaf6397e9a2f72f55 --- /dev/null +++ b/vm.args.dist @@ -0,0 +1,19 @@ ++W w +-kernel net_ticktime 60 +-env ERL_CRASH_DUMP log/crash.dump ++e 256000 +-env ERL_FULLSWEEP_AFTER 1000 +-mnesia dc_dump_limit 10 +-mnesia dump_log_write_threshold 700 +-mnesia dump_log_time_threshold 400000 ++Q 1048576 ++P 2560000 ++A 32 ++K true +-smp auto + +-setcookie ${ERL_CLUSTER_COOKIE} +-proto_dist inet_tcp +-name ${ERL_NODE_NAME} ++zdbbl 32768 +