aboutsummaryrefslogtreecommitdiff
path: root/server/src/jchat_methods.erl
diff options
context:
space:
mode:
Diffstat (limited to 'server/src/jchat_methods.erl')
-rw-r--r--server/src/jchat_methods.erl355
1 files changed, 355 insertions, 0 deletions
diff --git a/server/src/jchat_methods.erl b/server/src/jchat_methods.erl
new file mode 100644
index 0000000..67d9f13
--- /dev/null
+++ b/server/src/jchat_methods.erl
@@ -0,0 +1,355 @@
+-module(jchat_methods).
+
+-export([handle_method/3]).
+
+-include("jchat.hrl").
+
+%% Handle JMAP method calls
+handle_method(<<"Core/echo">>, Args, _AccountId) ->
+ {ok, Args};
+
+%% Conversation methods
+handle_method(<<"Conversation/get">>, Args, AccountId) ->
+ handle_conversation_get(Args, AccountId);
+handle_method(<<"Conversation/set">>, Args, AccountId) ->
+ handle_conversation_set(Args, AccountId);
+handle_method(<<"Conversation/changes">>, Args, AccountId) ->
+ handle_conversation_changes(Args, AccountId);
+handle_method(<<"Conversation/query">>, Args, AccountId) ->
+ handle_conversation_query(Args, AccountId);
+
+%% Message methods
+handle_method(<<"Message/get">>, Args, AccountId) ->
+ handle_message_get(Args, AccountId);
+handle_method(<<"Message/set">>, Args, AccountId) ->
+ handle_message_set(Args, AccountId);
+handle_method(<<"Message/changes">>, Args, AccountId) ->
+ handle_message_changes(Args, AccountId);
+handle_method(<<"Message/query">>, Args, AccountId) ->
+ handle_message_query(Args, AccountId);
+
+%% Participant methods
+handle_method(<<"Participant/get">>, Args, AccountId) ->
+ handle_participant_get(Args, AccountId);
+handle_method(<<"Participant/set">>, Args, AccountId) ->
+ handle_participant_set(Args, AccountId);
+
+%% Presence methods
+handle_method(<<"Presence/get">>, Args, AccountId) ->
+ handle_presence_get(Args, AccountId);
+handle_method(<<"Presence/set">>, Args, AccountId) ->
+ handle_presence_set(Args, AccountId);
+
+handle_method(Method, _Args, _AccountId) ->
+ {error, #{type => <<"unknownMethod">>,
+ description => <<"Unknown method: ", Method/binary>>}}.
+
+%% Conversation/get implementation
+handle_conversation_get(#{<<"accountId">> := AccountId} = Args, AccountId) ->
+ Ids = maps:get(<<"ids">>, Args, null),
+ Properties = maps:get(<<"properties">>, Args, null),
+
+ case get_conversations(Ids) of
+ {ok, Conversations} ->
+ List = [conversation_to_jmap(Conv, Properties) || Conv <- Conversations],
+ {ok, #{
+ <<"accountId">> => AccountId,
+ <<"state">> => get_state(conversation),
+ <<"list">> => List,
+ <<"notFound">> => []
+ }};
+ {error, Error} ->
+ {error, Error}
+ end;
+handle_conversation_get(_, _) ->
+ {error, account_not_found}.
+
+%% Conversation/set implementation
+handle_conversation_set(#{<<"accountId">> := AccountId} = Args, AccountId) ->
+ Create = maps:get(<<"create">>, Args, #{}),
+ Update = maps:get(<<"update">>, Args, #{}),
+ Destroy = maps:get(<<"destroy">>, Args, []),
+
+ {CreatedMap, NotCreated} = handle_conversation_creates(Create),
+ {UpdatedList, NotUpdated} = handle_conversation_updates(Update),
+ {DestroyedList, NotDestroyed} = handle_conversation_destroys(Destroy),
+
+ {ok, #{
+ <<"accountId">> => AccountId,
+ <<"oldState">> => get_state(conversation),
+ <<"newState">> => get_state(conversation),
+ <<"created">> => CreatedMap,
+ <<"updated">> => UpdatedList,
+ <<"destroyed">> => DestroyedList,
+ <<"notCreated">> => NotCreated,
+ <<"notUpdated">> => NotUpdated,
+ <<"notDestroyed">> => NotDestroyed
+ }};
+handle_conversation_set(_, _) ->
+ {error, account_not_found}.
+
+%% Message/get implementation
+handle_message_get(#{<<"accountId">> := AccountId} = Args, AccountId) ->
+ Ids = maps:get(<<"ids">>, Args, null),
+ Properties = maps:get(<<"properties">>, Args, null),
+
+ case get_messages(Ids) of
+ {ok, Messages} ->
+ List = [message_to_jmap(Msg, Properties) || Msg <- Messages],
+ {ok, #{
+ <<"accountId">> => AccountId,
+ <<"state">> => get_state(message),
+ <<"list">> => List,
+ <<"notFound">> => []
+ }};
+ {error, Error} ->
+ {error, Error}
+ end;
+handle_message_get(_, _) ->
+ {error, account_not_found}.
+
+%% Message/set implementation
+handle_message_set(#{<<"accountId">> := AccountId} = Args, AccountId) ->
+ Create = maps:get(<<"create">>, Args, #{}),
+ Update = maps:get(<<"update">>, Args, #{}),
+ Destroy = maps:get(<<"destroy">>, Args, []),
+
+ {CreatedMap, NotCreated} = handle_message_creates(Create),
+ {UpdatedList, NotUpdated} = handle_message_updates(Update),
+ {DestroyedList, NotDestroyed} = handle_message_destroys(Destroy),
+
+ {ok, #{
+ <<"accountId">> => AccountId,
+ <<"oldState">> => get_state(message),
+ <<"newState">> => get_state(message),
+ <<"created">> => CreatedMap,
+ <<"updated">> => UpdatedList,
+ <<"destroyed">> => DestroyedList,
+ <<"notCreated">> => NotCreated,
+ <<"notUpdated">> => NotUpdated,
+ <<"notDestroyed">> => NotDestroyed
+ }};
+handle_message_set(_, _) ->
+ {error, account_not_found}.
+
+handle_conversation_changes(_Args, _AccountId) ->
+ {ok, #{
+ <<"accountId">> => <<"default">>,
+ <<"oldState">> => <<"0">>,
+ <<"newState">> => get_state(conversation),
+ <<"hasMoreChanges">> => false,
+ <<"created">> => [],
+ <<"updated">> => [],
+ <<"destroyed">> => []
+ }}.
+
+handle_conversation_query(Args, AccountId) ->
+ case jchat_db:query_conversations(AccountId, Args) of
+ {ok, Conversations} ->
+ Ids = [Conv#conversation.id || Conv <- Conversations],
+ Total = length(Conversations),
+ {ok, #{
+ <<"accountId">> => AccountId,
+ <<"queryState">> => get_state(conversation),
+ <<"canCalculateChanges">> => true,
+ <<"position">> => 0,
+ <<"ids">> => Ids,
+ <<"total">> => Total
+ }};
+ {error, Error} ->
+ {error, Error}
+ end.
+
+handle_message_changes(_Args, _AccountId) ->
+ {ok, #{
+ <<"accountId">> => <<"default">>,
+ <<"oldState">> => <<"0">>,
+ <<"newState">> => get_state(message),
+ <<"hasMoreChanges">> => false,
+ <<"created">> => [],
+ <<"updated">> => [],
+ <<"destroyed">> => []
+ }}.
+
+handle_message_query(Args, AccountId) ->
+ Filter = maps:get(<<"filter">>, Args, #{}),
+ case jchat_db:query_messages(Filter, []) of
+ {ok, Messages} ->
+ Ids = [Msg#message.id || Msg <- Messages],
+ Total = length(Messages),
+ {ok, #{
+ <<"accountId">> => AccountId,
+ <<"queryState">> => get_state(message),
+ <<"canCalculateChanges">> => true,
+ <<"position">> => 0,
+ <<"ids">> => Ids,
+ <<"total">> => Total
+ }};
+ {error, Error} ->
+ {error, Error}
+ end.
+
+handle_participant_get(_Args, _AccountId) ->
+ {ok, #{
+ <<"accountId">> => <<"default">>,
+ <<"state">> => get_state(participant),
+ <<"list">> => [],
+ <<"notFound">> => []
+ }}.
+
+handle_participant_set(_Args, _AccountId) ->
+ {ok, #{
+ <<"accountId">> => <<"default">>,
+ <<"oldState">> => get_state(participant),
+ <<"newState">> => get_state(participant),
+ <<"created">> => #{},
+ <<"updated">> => [],
+ <<"destroyed">> => [],
+ <<"notCreated">> => #{},
+ <<"notUpdated">> => [],
+ <<"notDestroyed">> => []
+ }}.
+
+handle_presence_get(_Args, _AccountId) ->
+ {ok, #{
+ <<"accountId">> => <<"default">>,
+ <<"state">> => get_state(presence),
+ <<"list">> => [],
+ <<"notFound">> => []
+ }}.
+
+handle_presence_set(_Args, _AccountId) ->
+ {ok, #{
+ <<"accountId">> => <<"default">>,
+ <<"oldState">> => get_state(presence),
+ <<"newState">> => get_state(presence),
+ <<"created">> => #{},
+ <<"updated">> => [],
+ <<"destroyed">> => [],
+ <<"notCreated">> => #{},
+ <<"notUpdated">> => [],
+ <<"notDestroyed">> => []
+ }}.
+
+%% Helper functions
+get_conversations(null) ->
+ % Return all conversations (simplified)
+ {ok, []};
+get_conversations(Ids) ->
+ Results = [jchat_db:get_conversation(Id) || Id <- Ids],
+ Conversations = [Conv || {ok, Conv} <- Results],
+ {ok, Conversations}.
+
+get_messages(null) ->
+ {ok, []};
+get_messages(Ids) ->
+ Results = [jchat_db:get_message(Id) || Id <- Ids],
+ Messages = [Msg || {ok, Msg} <- Results],
+ {ok, Messages}.
+
+handle_conversation_creates(Creates) ->
+ maps:fold(fun(CreationId, ConvData, {CreatedAcc, NotCreatedAcc}) ->
+ Id = jchat_utils:generate_id(),
+ case jchat_db:create_conversation(Id, ConvData) of
+ {ok, Conv} ->
+ JMAPConv = conversation_to_jmap(Conv, null),
+ {CreatedAcc#{CreationId => JMAPConv}, NotCreatedAcc};
+ {error, Error} ->
+ {CreatedAcc, NotCreatedAcc#{CreationId => jchat_utils:format_error(Error)}}
+ end
+ end, {#{}, #{}}, Creates).
+
+handle_conversation_updates(Updates) ->
+ maps:fold(fun(Id, UpdateData, {UpdatedAcc, NotUpdatedAcc}) ->
+ case jchat_db:update_conversation(Id, UpdateData) of
+ {ok, Conv} ->
+ JMAPConv = conversation_to_jmap(Conv, null),
+ {[JMAPConv | UpdatedAcc], NotUpdatedAcc};
+ {error, Error} ->
+ {UpdatedAcc, NotUpdatedAcc#{Id => jchat_utils:format_error(Error)}}
+ end
+ end, {[], #{}}, Updates).
+
+handle_conversation_destroys(Destroy) ->
+ % Simplified - would implement actual deletion
+ {Destroy, #{}}.
+
+handle_message_creates(Creates) ->
+ maps:fold(fun(CreationId, MsgData, {CreatedAcc, NotCreatedAcc}) ->
+ Id = jchat_utils:generate_id(),
+ case jchat_db:create_message(Id, MsgData) of
+ {ok, Msg} ->
+ JMAPMsg = message_to_jmap(Msg, null),
+ {CreatedAcc#{CreationId => JMAPMsg}, NotCreatedAcc};
+ {error, Error} ->
+ {CreatedAcc, NotCreatedAcc#{CreationId => jchat_utils:format_error(Error)}}
+ end
+ end, {#{}, #{}}, Creates).
+
+handle_message_updates(Updates) ->
+ maps:fold(fun(Id, UpdateData, {UpdatedAcc, NotUpdatedAcc}) ->
+ case jchat_db:update_message(Id, UpdateData) of
+ {ok, Msg} ->
+ JMAPMsg = message_to_jmap(Msg, null),
+ {[JMAPMsg | UpdatedAcc], NotUpdatedAcc};
+ {error, Error} ->
+ {UpdatedAcc, NotUpdatedAcc#{Id => jchat_utils:format_error(Error)}}
+ end
+ end, {[], #{}}, Updates).
+
+handle_message_destroys(Destroy) ->
+ % Simplified - would implement actual deletion
+ {Destroy, #{}}.
+
+conversation_to_jmap(#conversation{} = Conv, Properties) ->
+ Base = #{
+ <<"id">> => Conv#conversation.id,
+ <<"title">> => Conv#conversation.title,
+ <<"description">> => Conv#conversation.description,
+ <<"createdAt">> => Conv#conversation.created_at,
+ <<"updatedAt">> => Conv#conversation.updated_at,
+ <<"isArchived">> => Conv#conversation.is_archived,
+ <<"isMuted">> => Conv#conversation.is_muted,
+ <<"participantIds">> => Conv#conversation.participant_ids,
+ <<"lastMessageId">> => Conv#conversation.last_message_id,
+ <<"lastMessageAt">> => Conv#conversation.last_message_at,
+ <<"unreadCount">> => Conv#conversation.unread_count,
+ <<"messageCount">> => Conv#conversation.message_count,
+ <<"metadata">> => Conv#conversation.metadata
+ },
+ filter_properties(Base, Properties).
+
+message_to_jmap(#message{} = Msg, Properties) ->
+ Base = #{
+ <<"id">> => Msg#message.id,
+ <<"conversationId">> => Msg#message.conversation_id,
+ <<"senderId">> => Msg#message.sender_id,
+ <<"sentAt">> => Msg#message.sent_at,
+ <<"receivedAt">> => Msg#message.received_at,
+ <<"editedAt">> => Msg#message.edited_at,
+ <<"body">> => Msg#message.body,
+ <<"bodyType">> => Msg#message.body_type,
+ <<"attachments">> => Msg#message.attachments,
+ <<"replyToMessageId">> => Msg#message.reply_to_message_id,
+ <<"isSystemMessage">> => Msg#message.is_system_message,
+ <<"isDeleted">> => Msg#message.is_deleted,
+ <<"reactions">> => Msg#message.reactions,
+ <<"deliveryStatus">> => Msg#message.delivery_status,
+ <<"readBy">> => Msg#message.read_by,
+ <<"metadata">> => Msg#message.metadata
+ },
+ filter_properties(Base, Properties).
+
+filter_properties(Map, null) ->
+ Map;
+filter_properties(Map, Properties) ->
+ maps:with(Properties, Map).
+
+get_state(Type) ->
+ Key = {<<"default">>, Type}, % {account_id, object_type}
+ case mnesia:dirty_read(state_counter, Key) of
+ [#state_counter{state = State}] ->
+ State;
+ [] ->
+ <<"0">>
+ end.