Skip to content

Commit

Permalink
Merge pull request #3563 from esl/graphql/muc-light-api-user
Browse files Browse the repository at this point in the history
Implement GraphQL MUC Light user queries
  • Loading branch information
chrzaszcz authored Mar 3, 2022
2 parents 5dd5d59 + 52f77be commit 08f95fa
Show file tree
Hide file tree
Showing 22 changed files with 1,180 additions and 233 deletions.
8 changes: 7 additions & 1 deletion big_tests/tests/graphql_helper.erl
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@

-export([execute/3, execute_auth/2, get_listener_port/1, get_listener_config/1]).
-export([init_admin_handler/1]).
-export([get_ok_value/2, get_err_msg/1]).
-export([get_ok_value/2, get_err_msg/1, make_creds/1]).

-include_lib("common_test/include/ct.hrl").
-include_lib("escalus/include/escalus.hrl").

-spec execute(atom(), binary(), {binary(), binary()} | undefined) ->
{Status :: tuple(), Data :: map()}.
Expand Down Expand Up @@ -71,6 +72,11 @@ get_ok_value([errors | Path], {{<<"200">>, <<"OK">>}, #{<<"errors">> := [Error]}
get_ok_value(Path, {{<<"200">>, <<"OK">>}, Data}) ->
get_value(Path, Data).

make_creds(#client{props = Props} = Client) ->
JID = escalus_utils:jid_to_lower(escalus_client:short_jid(Client)),
Password = proplists:get_value(password, Props),
{JID, Password}.

%% Internal

% Gets a nested value given a path
Expand Down
752 changes: 654 additions & 98 deletions big_tests/tests/graphql_muc_light_SUITE.erl

Large diffs are not rendered by default.

20 changes: 6 additions & 14 deletions priv/graphql/schemas/admin/muc_light.gql
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ Allow admin to manage Multi-User Chat Light rooms.
"""
type MUCLightAdminMutation @protected{
"Create a MUC light room under the given XMPP hostname"
createRoom(mucDomain: String!, name: String!, owner: JID!, subject: String!, id: String): Room
createRoom(mucDomain: String!, name: String!, owner: JID!, subject: String!, id: NonEmptyString): Room
"Change configuration of a MUC Light room"
changeRoomConfiguration(room: JID!, owner: JID!, name: String!, subject: String!): Room
"Invite a user to a MUC Light room"
Expand All @@ -14,30 +14,22 @@ type MUCLightAdminMutation @protected{
kickUser(room: JID!, user: JID!): String
"Send a message to a MUC Light room"
sendMessageToRoom(room: JID!, from: JID!, body: String!): String
"Set the user blocking list"
setBlockingList(user: JID!, items: [BlockingInput!]!): String
}

"""
Allow admin to get information about Multi-User Chat Light rooms.
"""
type MUCLightAdminQuery @protected{
"Get the MUC Light room archived messages"
getRoomMessages(room: JID!, pageSize: Int!, before: DateTime): StanzasPayload
getRoomMessages(room: JID!, pageSize: Int, before: DateTime): StanzasPayload
"Get configuration of the MUC Light room"
getRoomConfig(room: JID!): Room
"Get users list of given MUC Light room"
listRoomUsers(room: JID!): [RoomUser!]
"Get the list of MUC Light rooms that the user participates in"
listUserRooms(user: JID!): [JID!]
}

type Room{
jid: JID!
name: String!
subject: String!
participants: [RoomUser!]!
}

type RoomUser{
jid: JID!
affiliation: Affiliation!
"Get the user blocking list"
getBlockingList(user: JID!): [BlockingItem!]
}
34 changes: 34 additions & 0 deletions priv/graphql/schemas/global/muc.gql
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,37 @@ enum Affiliation{
MEMBER
NONE
}

input BlockingInput{
entityType: BlockedEntityType!
action: BlockingAction!
entity: JID!
}

type BlockingItem{
entityType: BlockedEntityType!
action: BlockingAction!
entity: JID!
}

enum BlockingAction{
ALLOW,
DENY
}

enum BlockedEntityType{
USER,
ROOM
}

type Room{
jid: JID!
name: String!
subject: String!
participants: [RoomUser!]!
}

type RoomUser{
jid: JID!
affiliation: Affiliation!
}
1 change: 1 addition & 0 deletions priv/graphql/schemas/global/scalar_types.gql
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
scalar DateTime
scalar Stanza
scalar JID
scalar NonEmptyString
35 changes: 35 additions & 0 deletions priv/graphql/schemas/user/muc_light.gql
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""
Allow user to manage Multi-User Chat Light rooms.
"""
type MUCLightUserMutation @protected{
"Create a MUC light room under the given XMPP hostname"
createRoom(mucDomain: String!, name: String!, subject: String!, id: NonEmptyString): Room
"Change configuration of a MUC Light room"
changeRoomConfiguration(room: JID!, name: String!, subject: String!): Room
"Invite a user to a MUC Light room"
inviteUser(room: JID!, recipient: JID!): String
"Remove a MUC Light room"
deleteRoom(room: JID!): String
"Kick a user from a MUC Light room"
kickUser(room: JID!, user: JID): String
"Send a message to a MUC Light room"
sendMessageToRoom(room: JID!, body: String!): String
"Set the user blocking list"
setBlockingList(items: [BlockingInput!]!): String
}

"""
Allow user to get information about Multi-User Chat Light rooms.
"""
type MUCLightUserQuery @protected{
"Get the MUC Light room archived messages"
getRoomMessages(room: JID!, pageSize: Int, before: DateTime): StanzasPayload
"Get configuration of the MUC Light room"
getRoomConfig(room: JID!): Room
"Get users list of given MUC Light room"
listRoomUsers(room: JID!): [RoomUser!]
"Get the list of MUC Light rooms that the user participates in"
listRooms: [JID!]
"Get the user blocking list"
getBlockingList: [BlockingItem!]
}
4 changes: 4 additions & 0 deletions priv/graphql/schemas/user/user_schema.gql
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ type UserQuery{
checkAuth: UserAuthInfo
"Account management"
account: AccountUserQuery
"MUC Light room management"
muc_light: MUCLightUserQuery
"Session management"
session: SessionUserQuery
}
Expand All @@ -23,4 +25,6 @@ Only an authenticated user can execute these mutations.
type UserMutation @protected{
"Account management"
account: AccountUserMutation
"MUC Light room management"
muc_light: MUCLightUserMutation
}
25 changes: 20 additions & 5 deletions src/graphql/admin/mongoose_graphql_muc_light_admin_mutation.erl
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@
-include("../mongoose_graphql_types.hrl").

-import(mongoose_graphql_helper, [make_error/2, format_result/2]).
-import(mongoose_graphql_muc_light_helper, [make_room/1, make_ok_user/1]).
-import(mongoose_graphql_muc_light_helper, [make_room/1, make_ok_user/1,
prepare_blocking_items/1]).

execute(_Ctx, _Obj, <<"createRoom">>, Args) ->
create_room(Args);
Expand All @@ -20,14 +21,22 @@ execute(_Ctx, _Obj, <<"deleteRoom">>, Args) ->
execute(_Ctx, _Obj, <<"kickUser">>, Args) ->
kick_user(Args);
execute(_Ctx, _Obj, <<"sendMessageToRoom">>, Args) ->
send_msg_to_room(Args).
send_msg_to_room(Args);
execute(_Ctx, _Obj, <<"setBlockingList">>, Args) ->
set_blocking_list(Args).

-spec create_room(map()) -> {ok, map()} | {error, resolver_error()}.
create_room(#{<<"id">> := null} = Args) ->
create_room(Args#{<<"id">> => <<>>});
create_room(#{<<"id">> := null, <<"mucDomain">> := MUCDomain, <<"name">> := RoomName,
<<"owner">> := CreatorJID, <<"subject">> := Subject}) ->
case mod_muc_light_api:create_room(MUCDomain, CreatorJID, RoomName, Subject) of
{ok, Room} ->
{ok, make_room(Room)};
Err ->
make_error(Err, #{mucDomain => MUCDomain, creator => CreatorJID})
end;
create_room(#{<<"id">> := RoomID, <<"mucDomain">> := MUCDomain, <<"name">> := RoomName,
<<"owner">> := CreatorJID, <<"subject">> := Subject}) ->
case mod_muc_light_api:create_room(MUCDomain, RoomID, RoomName, CreatorJID, Subject) of
case mod_muc_light_api:create_room(MUCDomain, RoomID, CreatorJID, RoomName, Subject) of
{ok, Room} ->
{ok, make_room(Room)};
Err ->
Expand Down Expand Up @@ -65,3 +74,9 @@ kick_user(#{<<"room">> := RoomJID, <<"user">> := UserJID}) ->
send_msg_to_room(#{<<"room">> := RoomJID, <<"from">> := FromJID, <<"body">> := Message}) ->
Result = mod_muc_light_api:send_message(RoomJID, FromJID, Message),
format_result(Result, #{room => jid:to_binary(RoomJID), from => jid:to_binary(FromJID)}).

-spec set_blocking_list(map()) -> {ok, binary()} | {error, resolver_error()}.
set_blocking_list(#{<<"user">> := UserJID, <<"items">> := Items}) ->
Items2 = prepare_blocking_items(Items),
Result = mod_muc_light_api:set_blocking(UserJID, Items2),
format_result(Result, #{user => jid:to_binary(UserJID)}).
31 changes: 21 additions & 10 deletions src/graphql/admin/mongoose_graphql_muc_light_admin_query.erl
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,10 @@
-include("../mongoose_graphql_types.hrl").

-import(mongoose_graphql_helper, [make_error/2, format_result/2]).
-import(mongoose_graphql_muc_light_helper, [make_room/1, make_ok_user/1]).
-import(mongoose_graphql_muc_light_helper, [make_room/1,
make_ok_user/1,
null_to_undefined/1,
page_size_or_max_limit/2]).

execute(_Ctx, _Obj, <<"listUserRooms">>, Args) ->
list_user_rooms(Args);
Expand All @@ -16,9 +19,11 @@ execute(_Ctx, _Obj, <<"listRoomUsers">>, Args) ->
execute(_Ctx, _Obj, <<"getRoomConfig">>, Args) ->
get_room_config(Args);
execute(_Ctx, _Obj, <<"getRoomMessages">>, Args) ->
get_room_messages(Args).
get_room_messages(Args);
execute(_Ctx, _Obj, <<"getBlockingList">>, Args) ->
get_blocking_list(Args).

-spec list_user_rooms(map()) -> {ok, [binary()]} | {error, resolver_error()}.
-spec list_user_rooms(map()) -> {ok, [{ok, binary()}]} | {error, resolver_error()}.
list_user_rooms(#{<<"user">> := UserJID}) ->
case mod_muc_light_api:get_user_rooms(UserJID) of
{ok, Rooms} ->
Expand All @@ -27,7 +32,7 @@ list_user_rooms(#{<<"user">> := UserJID}) ->
make_error(Err, #{user => UserJID})
end.

-spec list_room_users(map()) -> {ok, [map()]} | {error, resolver_error()}.
-spec list_room_users(map()) -> {ok, [{ok, map()}]} | {error, resolver_error()}.
list_room_users(#{<<"room">> := RoomJID}) ->
case mod_muc_light_api:get_room_aff(RoomJID) of
{ok, Affs} ->
Expand All @@ -49,15 +54,21 @@ get_room_config(#{<<"room">> := RoomJID}) ->
get_room_messages(#{<<"room">> := RoomJID, <<"pageSize">> := PageSize,
<<"before">> := Before}) ->
Before2 = null_to_undefined(Before),
case mod_muc_light_api:get_room_messages(RoomJID, PageSize, Before2) of
PageSize2 = page_size_or_max_limit(PageSize, 50),
case mod_muc_light_api:get_room_messages(RoomJID, PageSize2, Before2) of
{ok, Rows} ->
Maps = lists:map(fun mongoose_graphql_stanza_helper:row_to_map/1, Rows),
{ok, #{<<"stanzas">> => Maps, <<"limit">> => null}};
{ok, #{<<"stanzas">> => Maps, <<"limit">> => PageSize2}};
Err ->
make_error(Err, #{room => RoomJID})
end.

%% Helpers

null_to_undefined(null) -> undefined;
null_to_undefined(V) -> V.
-spec get_blocking_list(map()) -> {ok, [{ok, map()}]} | {error, resolver_error()}.
get_blocking_list(#{<<"user">> := UserJID}) ->
case mod_muc_light_api:get_blocking_list(UserJID) of
{ok, Items} ->
Items2 = lists:map(fun mongoose_graphql_muc_light_helper:blocking_item_to_map/1, Items),
{ok, Items2};
Err ->
make_error(Err, #{user => UserJID})
end.
2 changes: 2 additions & 0 deletions src/graphql/mongoose_graphql.erl
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ user_mapping_rules() ->
'UserMutation' => mongoose_graphql_user_mutation,
'AccountUserQuery' => mongoose_graphql_account_user_query,
'AccountUserMutation' => mongoose_graphql_account_user_mutation,
'MUCLightUserMutation' => mongoose_graphql_muc_light_user_mutation,
'MUCLightUserQuery' => mongoose_graphql_muc_light_user_query,
'SessionUserQuery' => mongoose_graphql_session_user_query,
'UserAuthInfo' => mongoose_graphql_user_auth_info,
default => mongoose_graphql_default},
Expand Down
12 changes: 10 additions & 2 deletions src/graphql/mongoose_graphql_enum.erl
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,11 @@ input(<<"AuthStatus">>, <<"AUTHORIZED">>) -> {ok, 'AUTHORIZED'};
input(<<"AuthStatus">>, <<"UNAUTHORIZED">>) -> {ok, 'UNAUTHORIZED'};
input(<<"Affiliation">>, <<"OWNER">>) -> {ok, owner};
input(<<"Affiliation">>, <<"MEMBER">>) -> {ok, member};
input(<<"Affiliation">>, <<"NONE">>) -> {ok, none}.
input(<<"Affiliation">>, <<"NONE">>) -> {ok, none};
input(<<"BlockingAction">>, <<"ALLOW">>) -> {ok, allow};
input(<<"BlockingAction">>, <<"DENY">>) -> {ok, deny};
input(<<"BlockedEntityType">>, <<"USER">>) -> {ok, user};
input(<<"BlockedEntityType">>, <<"ROOM">>) -> {ok, room}.

output(<<"PresenceShow">>, Show) ->
{ok, list_to_binary(string:to_upper(binary_to_list(Show)))};
Expand All @@ -21,4 +25,8 @@ output(<<"PresenceType">>, Type) ->
output(<<"AuthStatus">>, Status) ->
{ok, atom_to_binary(Status, utf8)};
output(<<"Affiliation">>, Aff) ->
{ok, list_to_binary(string:to_upper(atom_to_list(Aff)))}.
{ok, list_to_binary(string:to_upper(atom_to_list(Aff)))};
output(<<"BlockingAction">>, Action) ->
{ok, list_to_binary(string:to_upper(atom_to_list(Action)))};
output(<<"BlockedEntityType">>, What) ->
{ok, list_to_binary(string:to_upper(atom_to_list(What)))}.
22 changes: 21 additions & 1 deletion src/graphql/mongoose_graphql_muc_light_helper.erl
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
-module(mongoose_graphql_muc_light_helper).

-export([make_room/1, make_ok_user/1]).
-export([make_room/1, make_ok_user/1, blocking_item_to_map/1,
null_to_undefined/1, prepare_blocking_items/1,
page_size_or_max_limit/2]).

-spec page_size_or_max_limit(null | integer(), integer()) -> integer().
page_size_or_max_limit(null, MaxLimit) ->
MaxLimit;
page_size_or_max_limit(PageSize, MaxLimit) when PageSize > MaxLimit ->
MaxLimit;
page_size_or_max_limit(PageSize, _MaxLimit) ->
PageSize.

-spec make_room(mod_muc_light_api:room()) -> map().
make_room(#{jid := JID, name := Name, subject := Subject, aff_users := Users}) ->
Expand All @@ -10,3 +20,13 @@ make_room(#{jid := JID, name := Name, subject := Subject, aff_users := Users}) -

make_ok_user({JID, Aff}) ->
{ok, #{<<"jid">> => JID, <<"affiliation">> => Aff}}.

prepare_blocking_items(Items) ->
[{What, Action, jid:to_lus(Who)} || #{<<"entity">> := Who, <<"entityType">> := What,
<<"action">> := Action} <- Items].

blocking_item_to_map({What, Action, Who}) ->
{ok, #{<<"entityType">> => What, <<"action">> => Action, <<"entity">> => Who}}.

null_to_undefined(null) -> undefined;
null_to_undefined(V) -> V.
12 changes: 12 additions & 0 deletions src/graphql/mongoose_graphql_scalar.erl
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
input(<<"DateTime">>, DT) -> binary_to_microseconds(DT);
input(<<"Stanza">>, Value) -> exml:parse(Value);
input(<<"JID">>, Jid) -> jid_from_binary(Jid);
input(<<"NonEmptyString">>, Value) -> non_empty_string_to_binary(Value);
input(Ty, V) ->
error_logger:info_report({coercing_generic_scalar, Ty, V}),
{ok, V}.
Expand All @@ -24,6 +25,7 @@ input(Ty, V) ->
output(<<"DateTime">>, DT) -> {ok, microseconds_to_binary(DT)};
output(<<"Stanza">>, Elem) -> {ok, exml:to_binary(Elem)};
output(<<"JID">>, Jid) -> {ok, jid:to_binary(Jid)};
output(<<"NonEmptyString">>, Value) -> binary_to_non_empty_string(Value);
output(Ty, V) ->
error_logger:info_report({output_generic_scalar, Ty, V}),
{ok, V}.
Expand All @@ -44,6 +46,16 @@ binary_to_microseconds(DT) ->
{ok, Microseconds}
end.

non_empty_string_to_binary(<<>>) ->
{error, "Given string is empty"};
non_empty_string_to_binary(String) ->
{ok, String}.

binary_to_non_empty_string(<<>>) ->
{error, "Empty binary cannot be converted to NonEmptyString"};
binary_to_non_empty_string(Val) ->
{ok, Val}.

microseconds_to_binary(Microseconds) ->
Opts = [{offset, "Z"}, {unit, microsecond}],
list_to_binary(calendar:system_time_to_rfc3339(Microseconds, Opts)).
Loading

0 comments on commit 08f95fa

Please sign in to comment.