Skip to content

Commit b70a11d

Browse files
feat: add update and delete messages
1 parent 5da5b94 commit b70a11d

File tree

5 files changed

+209
-50
lines changed

5 files changed

+209
-50
lines changed

services/web/server/src/simcore_service_webserver/conversations/_conversation_message_service.py

Lines changed: 36 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
# pylint: disable=unused-argument
22

3-
import asyncio
43
import logging
54

65
from aiohttp import web
@@ -15,33 +14,28 @@
1514
from models_library.projects import ProjectID
1615
from models_library.rest_ordering import OrderBy, OrderDirection
1716
from models_library.rest_pagination import PageTotalCount
18-
from models_library.socketio import SocketMessageDict
1917
from models_library.users import UserID
2018

2119
from ..projects._groups_repository import list_project_groups
2220

2321
# Import or define SocketMessageDict
24-
from ..socketio.messages import (
25-
SOCKET_IO_PROJECT_CONVERSATION_MESSAGE_CREATED_EVENT,
26-
send_message_to_standard_group,
27-
)
2822
from ..users.api import get_user_primary_group_id
2923
from . import _conversation_message_repository
24+
from ._socketio import (
25+
notify_conversation_message_created,
26+
notify_conversation_message_deleted,
27+
notify_conversation_message_updated,
28+
)
3029

3130
_logger = logging.getLogger(__name__)
3231

3332

34-
def _make_project_conversation_message_created_message(
35-
project_id: ProjectID,
36-
conversation_message: ConversationMessageGetDB,
37-
) -> SocketMessageDict:
38-
return SocketMessageDict(
39-
event_type=SOCKET_IO_PROJECT_CONVERSATION_MESSAGE_CREATED_EVENT,
40-
data={
41-
"project_id": project_id,
42-
**conversation_message.model_dump(mode="json"),
43-
},
44-
)
33+
async def _get_recipients(app, project_id):
34+
return [
35+
project_to_group.gid
36+
for project_to_group in await list_project_groups(app, project_id=project_id)
37+
if project_to_group.read
38+
]
4539

4640

4741
async def create_message(
@@ -64,20 +58,11 @@ async def create_message(
6458
type_=type_,
6559
)
6660

67-
notification_recipients = [
68-
project_to_group.gid
69-
for project_to_group in await list_project_groups(app, project_id=project_id)
70-
if project_to_group.read
71-
]
72-
73-
notification_message = _make_project_conversation_message_created_message(
74-
project_id, created_message
75-
)
76-
await asyncio.gather(
77-
*[
78-
send_message_to_standard_group(app, recipient, notification_message)
79-
for recipient in notification_recipients
80-
]
61+
await notify_conversation_message_created(
62+
app,
63+
recipients=await _get_recipients(app, project_id),
64+
project_id=project_id,
65+
conversation_message=created_message,
8166
)
8267

8368
return created_message
@@ -97,22 +82,33 @@ async def get_message(
9782
async def update_message(
9883
app: web.Application,
9984
*,
85+
project_id: ProjectID,
10086
conversation_id: ConversationID,
10187
message_id: ConversationMessageID,
10288
# Update attributes
10389
updates: ConversationMessagePatchDB,
10490
) -> ConversationMessageGetDB:
105-
return await _conversation_message_repository.update(
91+
updated_message = await _conversation_message_repository.update(
10692
app,
10793
conversation_id=conversation_id,
10894
message_id=message_id,
10995
updates=updates,
11096
)
11197

98+
await notify_conversation_message_updated(
99+
app,
100+
recipients=await _get_recipients(app, project_id),
101+
project_id=project_id,
102+
conversation_message=updated_message,
103+
)
104+
105+
return updated_message
106+
112107

113108
async def delete_message(
114109
app: web.Application,
115110
*,
111+
project_id: ProjectID,
116112
conversation_id: ConversationID,
117113
message_id: ConversationMessageID,
118114
) -> None:
@@ -122,6 +118,14 @@ async def delete_message(
122118
message_id=message_id,
123119
)
124120

121+
await notify_conversation_message_deleted(
122+
app,
123+
recipients=await _get_recipients(app, project_id),
124+
project_id=project_id,
125+
conversation_id=conversation_id,
126+
message_id=message_id,
127+
)
128+
125129

126130
async def list_messages_for_conversation(
127131
app: web.Application,
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
import asyncio
2+
import datetime
3+
from typing import Final
4+
5+
from aiohttp import web
6+
from models_library.conversations import (
7+
ConversationID,
8+
ConversationMessageGetDB,
9+
ConversationMessageID,
10+
)
11+
from models_library.groups import GroupID
12+
from models_library.projects import ProjectID
13+
from models_library.socketio import SocketMessageDict
14+
from pydantic import BaseModel
15+
16+
from ..socketio.messages import send_message_to_standard_group
17+
18+
SOCKET_IO_CONVERSATION_MESSAGE_CREATED_EVENT: Final[str] = "conversationMessageCreated"
19+
20+
SOCKET_IO_CONVERSATION_MESSAGE_DELETED_EVENT: Final[str] = "conversationMessageDeleted"
21+
22+
SOCKET_IO_CONVERSATION_MESSAGE_UPDATED_EVENT: Final[str] = "conversationMessageUpdated"
23+
24+
25+
class ConversationMessage(BaseModel):
26+
conversation_id: ConversationID
27+
message_id: ConversationMessageID
28+
29+
30+
class ConversationMessageCreated(ConversationMessage):
31+
content: str
32+
created: datetime.datetime
33+
34+
35+
class ConversationMessageUpdated(ConversationMessage):
36+
content: str
37+
modified: datetime.datetime
38+
39+
40+
class ConversationMessageDeleted(ConversationMessage): ...
41+
42+
43+
async def _send_message_to_recipients(app, recipients, notification_message):
44+
await asyncio.gather(
45+
*[
46+
send_message_to_standard_group(app, recipient, notification_message)
47+
for recipient in recipients
48+
]
49+
)
50+
51+
52+
async def notify_conversation_message_created(
53+
app: web.Application,
54+
*,
55+
recipients: list[GroupID],
56+
project_id: ProjectID,
57+
conversation_message: ConversationMessageGetDB,
58+
) -> None:
59+
notification_message = SocketMessageDict(
60+
event_type=SOCKET_IO_CONVERSATION_MESSAGE_CREATED_EVENT,
61+
data={
62+
"project_id": project_id,
63+
**ConversationMessageCreated(
64+
**conversation_message.model_dump()
65+
).model_dump(mode="json"),
66+
},
67+
)
68+
69+
await _send_message_to_recipients(app, recipients, notification_message)
70+
71+
72+
async def notify_conversation_message_updated(
73+
app: web.Application,
74+
*,
75+
recipients: list[GroupID],
76+
project_id: ProjectID,
77+
conversation_message: ConversationMessageGetDB,
78+
) -> None:
79+
80+
notification_message = SocketMessageDict(
81+
event_type=SOCKET_IO_CONVERSATION_MESSAGE_UPDATED_EVENT,
82+
data={
83+
"project_id": project_id,
84+
**ConversationMessageUpdated(
85+
**conversation_message.model_dump()
86+
).model_dump(mode="json"),
87+
},
88+
)
89+
90+
await _send_message_to_recipients(app, recipients, notification_message)
91+
92+
93+
async def notify_conversation_message_deleted(
94+
app: web.Application,
95+
*,
96+
recipients: list[GroupID],
97+
project_id: ProjectID,
98+
conversation_id: ConversationID,
99+
message_id: ConversationMessageID,
100+
) -> None:
101+
102+
notification_message = SocketMessageDict(
103+
event_type=SOCKET_IO_CONVERSATION_MESSAGE_DELETED_EVENT,
104+
data={
105+
"project_id": project_id,
106+
**ConversationMessageDeleted(
107+
conversation_id=conversation_id, message_id=message_id
108+
).model_dump(mode="json"),
109+
},
110+
)
111+
112+
await _send_message_to_recipients(app, recipients, notification_message)

services/web/server/src/simcore_service_webserver/projects/_conversations_service.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ async def update_project_conversation_message(
222222
)
223223
return await conversations_service.update_message(
224224
app,
225+
project_id=project_uuid,
225226
conversation_id=conversation_id,
226227
message_id=message_id,
227228
updates=ConversationMessagePatchDB(content=content),
@@ -245,7 +246,10 @@ async def delete_project_conversation_message(
245246
permission="read",
246247
)
247248
await conversations_service.delete_message(
248-
app, conversation_id=conversation_id, message_id=message_id
249+
app,
250+
project_id=project_uuid,
251+
conversation_id=conversation_id,
252+
message_id=message_id,
249253
)
250254

251255

services/web/server/src/simcore_service_webserver/socketio/messages.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,6 @@
2828

2929
SOCKET_IO_NODE_UPDATED_EVENT: Final[str] = "nodeUpdated"
3030

31-
SOCKET_IO_PROJECT_CONVERSATION_MESSAGE_CREATED_EVENT: Final[str] = (
32-
"projectConversationMessageCreated"
33-
)
3431
SOCKET_IO_PROJECT_UPDATED_EVENT: Final[str] = "projectStateUpdated"
3532

3633
SOCKET_IO_WALLET_OSPARC_CREDITS_UPDATED_EVENT: Final[str] = "walletOsparcCreditsUpdated"

services/web/server/tests/unit/with_dbs/02/test_projects_conversations_handlers.py

Lines changed: 56 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
from unittest.mock import MagicMock
1111

1212
import pytest
13+
import simcore_service_webserver.conversations._conversation_message_service
1314
import sqlalchemy as sa
1415
from aiohttp.test_utils import TestClient
1516
from models_library.api_schemas_webserver.projects_conversations import (
@@ -31,11 +32,32 @@
3132

3233

3334
@pytest.fixture
34-
def mocked_notify_project_conversation_message_created(
35+
def mocked_notify_conversation_message_created(
3536
mocker: MockerFixture,
3637
) -> MagicMock:
37-
return mocker.patch(
38-
"simcore_service_webserver.conversations._conversation_message_service._make_project_conversation_message_created_message",
38+
return mocker.patch.object(
39+
simcore_service_webserver.conversations._conversation_message_service,
40+
"notify_conversation_message_created",
41+
)
42+
43+
44+
@pytest.fixture
45+
def mocked_notify_conversation_message_updated(
46+
mocker: MockerFixture,
47+
) -> MagicMock:
48+
return mocker.patch.object(
49+
simcore_service_webserver.conversations._conversation_message_service,
50+
"notify_conversation_message_updated",
51+
)
52+
53+
54+
@pytest.fixture
55+
def mocked_notify_conversation_message_deleted(
56+
mocker: MockerFixture,
57+
) -> MagicMock:
58+
return mocker.patch.object(
59+
simcore_service_webserver.conversations._conversation_message_service,
60+
"notify_conversation_message_deleted",
3961
)
4062

4163

@@ -174,7 +196,9 @@ async def test_project_conversation_messages_full_workflow(
174196
user_project: ProjectDict,
175197
expected: HTTPStatus,
176198
postgres_db: sa.engine.Engine,
177-
mocked_notify_project_conversation_message_created: MagicMock,
199+
mocked_notify_conversation_message_created: MagicMock,
200+
mocked_notify_conversation_message_updated: MagicMock,
201+
mocked_notify_conversation_message_deleted: MagicMock,
178202
):
179203
base_project_url = client.app.router["list_project_conversations"].url_for(
180204
project_id=user_project["uuid"]
@@ -203,12 +227,11 @@ async def test_project_conversation_messages_full_workflow(
203227
assert ConversationMessageRestGet.model_validate(data)
204228
_first_message_id = data["messageId"]
205229

206-
assert mocked_notify_project_conversation_message_created.call_count == 1
207-
args = mocked_notify_project_conversation_message_created.call_args
230+
assert mocked_notify_conversation_message_created.call_count == 1
231+
kwargs = mocked_notify_conversation_message_created.await_args[1]
208232

209-
assert user_project["uuid"] == f"{args[0][0]}"
210-
assert args[0][1].content == "My first message"
211-
assert args[0][1].type == "MESSAGE"
233+
assert f"{kwargs['project_id']}" == user_project["uuid"]
234+
assert kwargs["conversation_message"].content == "My first message"
212235

213236
# Now we will add second message
214237
body = {"content": "My second message", "type": "MESSAGE"}
@@ -220,12 +243,11 @@ async def test_project_conversation_messages_full_workflow(
220243
assert ConversationMessageRestGet.model_validate(data)
221244
_second_message_id = data["messageId"]
222245

223-
assert mocked_notify_project_conversation_message_created.call_count == 2
224-
args = mocked_notify_project_conversation_message_created.call_args
246+
assert mocked_notify_conversation_message_created.call_count == 2
247+
kwargs = mocked_notify_conversation_message_created.await_args[1]
225248

226-
assert user_project["uuid"] == f"{args[0][0]}"
227-
assert args[0][1].content == "My second message"
228-
assert args[0][1].type == "MESSAGE"
249+
assert user_project["uuid"] == f"{kwargs['project_id']}"
250+
assert kwargs["conversation_message"].content == "My second message"
229251

230252
# Now we will list all message for the project conversation
231253
resp = await client.get(f"{base_project_conversation_url}")
@@ -253,6 +275,12 @@ async def test_project_conversation_messages_full_workflow(
253275
expected,
254276
)
255277

278+
assert mocked_notify_conversation_message_updated.call_count == 1
279+
kwargs = mocked_notify_conversation_message_updated.await_args[1]
280+
281+
assert user_project["uuid"] == f"{kwargs['project_id']}"
282+
assert kwargs["conversation_message"].content == updated_content
283+
256284
# Get the second message
257285
resp = await client.get(f"{base_project_conversation_url}/{_second_message_id}")
258286
data, _ = await assert_status(
@@ -283,6 +311,13 @@ async def test_project_conversation_messages_full_workflow(
283311
status.HTTP_204_NO_CONTENT,
284312
)
285313

314+
assert mocked_notify_conversation_message_deleted.call_count == 1
315+
kwargs = mocked_notify_conversation_message_deleted.await_args[1]
316+
317+
assert f"{kwargs['project_id']}" == user_project["uuid"]
318+
assert f"{kwargs['conversation_id']}" == _conversation_id
319+
assert f"{kwargs['message_id']}" == _second_message_id
320+
286321
# Now we will list all message for the project conversation
287322
resp = await client.get(f"{base_project_conversation_url}")
288323
data, _, meta = await assert_status(resp, expected, include_meta=True)
@@ -373,3 +408,10 @@ async def test_project_conversation_messages_full_workflow(
373408
resp,
374409
status.HTTP_204_NO_CONTENT,
375410
)
411+
412+
assert mocked_notify_conversation_message_deleted.call_count == 2
413+
kwargs = mocked_notify_conversation_message_deleted.await_args[1]
414+
415+
assert f"{kwargs['project_id']}" == user_project["uuid"]
416+
assert f"{kwargs['conversation_id']}" == _conversation_id
417+
assert f"{kwargs['message_id']}" == _first_message_id

0 commit comments

Comments
 (0)