Skip to content

Commit

Permalink
feature: like message (#31)
Browse files Browse the repository at this point in the history
* feature: handle users registration

* refactor: refactor newcomer notification

* tests: add users route test

* tests: add newcomer registration tests

* fix: set newcomer field validators

* feature: notify room when a user has left

* feature: notify room when a user has left in web page

* doc: update README.md with new message types

* doc: add docstrings

* feature: notify user when a message is liked

* tests: add message like test

* tests: add exiter test
  • Loading branch information
gounux authored Oct 25, 2024
1 parent aca9c13 commit bbf740f
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 1 deletion.
34 changes: 34 additions & 0 deletions gischat/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
from gischat import INTERNAL_MESSAGE_AUTHOR
from gischat.models import (
InternalExiterMessageModel,
InternalLikeMessageModel,
InternalNbUsersMessageModel,
InternalNewcomerMessageModel,
MessageModel,
Expand Down Expand Up @@ -193,6 +194,24 @@ def is_user_present(self, room: str, user: str) -> bool:
continue
return False

async def notify_user(self, room: str, user: str, message: str) -> None:
"""
Notifies a user in a room with a "private" message
Private means only this user is notified of the message
:param room: room
:param user: user to notify
:param message: message to send
"""
for ws in self.connections[room]:
try:
if self.users[ws] == user:
try:
await ws.send_text(message)
except WebSocketDisconnect:
logger.error("Can not send message to disconnected websocket")
except KeyError:
continue


notifier = WebsocketNotifier()

Expand Down Expand Up @@ -290,13 +309,28 @@ async def websocket_endpoint(websocket: WebSocket, room: str) -> None:
while True:
data = await websocket.receive_text()
payload = json.loads(data)

# handle internal messages
if "author" in payload and payload["author"] == "internal":

# registration messages
if "newcomer" in payload:
newcomer = payload["newcomer"]
notifier.register_user(websocket, newcomer)
logger.info(f"Newcomer in room {room}: {newcomer}")
await notifier.notify_newcomer(room, newcomer)

# like messages
if "liked_author" in payload and "liker_author" in payload:
message = InternalLikeMessageModel(**payload)
logger.info(
f"{message.liker_author} liked {message.liked_author}'s message ({message.message})"
)
await notifier.notify_user(
room,
message.liked_author,
json.dumps(jsonable_encoder(message)),
)
else:
try:
message = MessageModel(**payload)
Expand Down
19 changes: 19 additions & 0 deletions gischat/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,22 @@ class InternalExiterMessageModel(BaseModel):
max_length=int(os.environ.get("MAX_AUTHOR_LENGTH", 32)),
pattern=r"^[a-z-A-Z-0-9-_]+$",
)


class InternalLikeMessageModel(BaseModel):
author: str
message: str = Field(
None, max_length=int(os.environ.get("MAX_MESSAGE_LENGTH", 255))
)
liker_author: str = Field(
None,
min_length=int(os.environ.get("MIN_AUTHOR_LENGTH", 3)),
max_length=int(os.environ.get("MAX_AUTHOR_LENGTH", 32)),
pattern=r"^[a-z-A-Z-0-9-_]+$",
)
liked_author: str = Field(
None,
min_length=int(os.environ.get("MIN_AUTHOR_LENGTH", 3)),
max_length=int(os.environ.get("MAX_AUTHOR_LENGTH", 32)),
pattern=r"^[a-z-A-Z-0-9-_]+$",
)
6 changes: 5 additions & 1 deletion gischat/templates/ws-page.html
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,13 @@ <h1>gischat web client</h1>
}
const ws_protocol = ssl.checked ? "wss" : "ws";
const ws_url = `${ws_protocol}://${instance.value}/room/${room.value}/ws`;
const user = document.getElementById("authorId").value;
websocket = new WebSocket(ws_url);
websocket.onopen = (event) => {
displayMessage(`Connected to websocket in room ${room.value}`);
connected = true;
setFormEnabled(true);
websocket.send(JSON.stringify({author: "internal", newcomer: document.getElementById("authorId").value}));
websocket.send(JSON.stringify({author: "internal", newcomer: user}));
}
websocket.onmessage = (event) => {
const data = JSON.parse(event.data);
Expand All @@ -127,6 +128,9 @@ <h1>gischat web client</h1>
if (data.exiter) {
log = `${data.exiter} has left the room`;
}
if (data.liker_author && data.liked_author && data.liked_author === user){
log = `${data.liker_author} liked your message "${data.message}"`;
}
} else {
log = `[${author}] (${new Date().toLocaleTimeString()}): ${data.message}`
}
Expand Down
77 changes: 77 additions & 0 deletions tests/test_websocket.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,14 @@ def test_websocket_send_newcomer_multiple(client: TestClient, room: str):
"author": INTERNAL_MESSAGE_AUTHOR,
"newcomer": "user2",
}
assert websocket1.receive_json() == {
"author": INTERNAL_MESSAGE_AUTHOR,
"exiter": "user2",
}
assert websocket1.receive_json() == {
"author": INTERNAL_MESSAGE_AUTHOR,
"nb_users": 1,
}


@pytest.mark.parametrize("room", test_rooms())
Expand All @@ -163,3 +171,72 @@ def test_websocket_send_newcomer_api_call(client: TestClient, room: str):
{"author": INTERNAL_MESSAGE_AUTHOR, "newcomer": "Barnabe"}
)
assert client.get(f"/room/{room}/users").json() == ["Barnabe", "Isidore"]


@pytest.mark.parametrize("room", test_rooms())
def test_websocket_like_message(client: TestClient, room: str):
# register client 1 (Isidore)
with client.websocket_connect(f"/room/{room}/ws") as websocket1:
assert websocket1.receive_json() == {
"author": INTERNAL_MESSAGE_AUTHOR,
"nb_users": 1,
}
websocket1.send_json({"author": INTERNAL_MESSAGE_AUTHOR, "newcomer": "Isidore"})
assert websocket1.receive_json() == {
"author": INTERNAL_MESSAGE_AUTHOR,
"newcomer": "Isidore",
}

# register client 2 (Barnabe)
with client.websocket_connect(f"/room/{room}/ws") as websocket2:
assert websocket1.receive_json() == {
"author": INTERNAL_MESSAGE_AUTHOR,
"nb_users": 2,
}
assert websocket2.receive_json() == {
"author": INTERNAL_MESSAGE_AUTHOR,
"nb_users": 2,
}

websocket2.send_json(
{"author": INTERNAL_MESSAGE_AUTHOR, "newcomer": "Barnabe"}
)
assert websocket1.receive_json() == {
"author": INTERNAL_MESSAGE_AUTHOR,
"newcomer": "Barnabe",
}
assert websocket2.receive_json() == {
"author": INTERNAL_MESSAGE_AUTHOR,
"newcomer": "Barnabe",
}

# client 1 sends a message
websocket1.send_json(
{"author": "Isidore", "message": "hi", "avatar": "postgis"}
)
assert websocket1.receive_json() == {
"author": "Isidore",
"message": "hi",
"avatar": "postgis",
}
assert websocket2.receive_json() == {
"author": "Isidore",
"message": "hi",
"avatar": "postgis",
}

# client 2 likes client 1's message
websocket2.send_json(
{
"author": INTERNAL_MESSAGE_AUTHOR,
"message": "hi",
"liker_author": "Barnabe",
"liked_author": "Isidore",
}
)
assert websocket1.receive_json() == {
"author": INTERNAL_MESSAGE_AUTHOR,
"message": "hi",
"liker_author": "Barnabe",
"liked_author": "Isidore",
}

0 comments on commit bbf740f

Please sign in to comment.