From 49fa5aa2a127bdf8924d02bf77e5086b39c7a447 Mon Sep 17 00:00:00 2001 From: Calvin Morrison Date: Wed, 3 Sep 2025 21:15:36 -0400 Subject: i vibe coded it --- server/src/jchat_methods.erl | 355 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 355 insertions(+) create mode 100644 server/src/jchat_methods.erl (limited to 'server/src/jchat_methods.erl') 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. -- cgit v1.2.3