Skip to content

Implemented mongodb schema for messaging #48#105

Open
HlibSokol wants to merge 1 commit into
mainfrom
messaging-db-schema-48
Open

Implemented mongodb schema for messaging #48#105
HlibSokol wants to merge 1 commit into
mainfrom
messaging-db-schema-48

Conversation

@HlibSokol

@HlibSokol HlibSokol commented Jun 16, 2025

Copy link
Copy Markdown
Collaborator
  • installed mongoengine and configured docker-compose.yml and settings.py for mongoDB usage
  • modified routing.py for better use and added path for rooms; updated requirements.txt
  • added specific models_mongo.py with Room and Message models
  • created consumers.py with configured ChatConsumer to manage WebSocket connections and messages
  • created middleware.py with JwtAuthMiddleware to manage if users that are connecting to rooms via WebSocket are authenticated (registered and logged in==obtained token)
  • updated asgi.py with JwtAuthMiddleware to validate ?token= on connect

tested in postman:

image
image

checking history using mongoDB extension in VisualStudio:

image
image

@HlibSokol HlibSokol added this to the Sprint 3 milestone Jun 16, 2025
@HlibSokol HlibSokol self-assigned this Jun 16, 2025
@ArsenStrus ArsenStrus self-requested a review June 16, 2025 15:33
Comment thread docker-compose.yml
@@ -37,7 +37,15 @@ services:
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please check if Docker is working properly.
Replacement: $$ is only escaped in bash, but here it is necessary for Docker to interpret environment variables correctly. That is, leave one $.

Comment thread docker-compose.yml

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add healthcheck for MongoDB (optional)
healthcheck:
test: echo 'db.runCommand("ping").ok' | mongosh localhost:27017/test --quiet
interval: 10s
retries: 5

room.participants.append(user_pk)
room.updated_at = timezone.now()

room.save()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing await in room.save() and Message(...).save()
MongoEngine .save() is not an asynchronous function. In asynchronous code, this can block the event loop.

Solution: move from async def to a separate sync_to_async block:

from asgiref.sync import sync_to_async

@sync_to_async
def save_room_and_message(room, user_pk, text):
room.save()
Message(
room=room,
sender_id=user_pk,
text=text,
timestamp=timezone.now()
).save()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And in receive():
await save_room_and_message(room, user_pk, text)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No channel_layer check
Although rare, self.channel_layer can be None if Redis (or another backend) is not configured.
if not self.channel_layer:
await self.send(text_data="No channel layer configured.")
return

user_pk = user.pk

room = Room.objects(room_id=self.room_id).first()

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Repetitive code in Room logic can be moved to a helper function for cleanliness:
@sync_to_async
def get_or_update_room(room_id, user_pk):
room = Room.objects(room_id=room_id).first()
if not room:
room = Room(room_id=room_id, participants=[user_pk])
else:
if user_pk not in room.participants:
room.participants.append(user_pk)
room.updated_at = timezone.now()
room.save()
return room

room_id = me.StringField(primary_key=True, required=True)
participants = me.ListField(me.IntField(), default=list, required=True)
created_at = me.DateTimeField(required=True, default=timezone.now)
updated_at = me.DateTimeField(required=True, default=timezone.now)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Adding auto_now for updated_at makes your logic store updated_at manually, but it's easy to forget. It's worth creating a "hook" to make this happen automatically.
def save(self, *args, **kwargs):
self.updated_at = timezone.now()
return super().save(*args, **kwargs)

]
}
room = me.ReferenceField(Room, required=True, reverse_delete_rule=me.CASCADE)
sender_id = me.IntField(required=True)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use ReferenceField for sender_id if users are stored in Mongo — it's worth:
sender = me.ReferenceField(User, required=True)

"db_alias": "chat",
"collection": "messages",
"indexes": [
{"fields": ["room", "timestamp"]},

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is good, but you can also add something "-" to quickly sort backwards in time:
{"fields": ["room", "-timestamp"]}

sender_id = me.IntField(required=True)
text = me.StringField(required=True)
timestamp = me.DateTimeField(required=True, default=timezone.now)

No newline at end of file

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If there is default=timezone.now, then required=True is not required.

Comment thread forum/forum/asgi.py

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

AuthMiddlewareStack already tries to authenticate via session or cookie, but if you are working exclusively with JWT via query param, then AuthMiddlewareStack may be unnecessary.

If you are definitely not using cookie-based login:

"websocket": JwtAuthMiddleware(
URLRouter(websocket_urlpatterns)
)
This will speed up the authorization of WebSocket requests and reduce unnecessary load.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

3 participants