Messaging System Backend

The app supports real-time messaging, where users can send personal messages to each other, or create group chats to have conversations between multiple team members.

📘 Backend Documentation: Group and Personal Chat System

Overview

This backend supports:

  • Personal and group messaging

  • Real-time-like polling with intelligent refresh

  • Group creation via modal with user search

  • Chat UI state preservation via query param (currentChatId)

  • Profile image, message state (sent, delivered, read)

  • PostgreSQL ENUMs and relationships via SQLAlchemy


🗂️ Database Models

User

Standard user model with profile data. Fields typically include:

id: Integer, primary key
username: String
first_name: String
last_name: String
image_route: String

MessageState ENUM

msg_state_enum = ENUM('sent', 'delivered', 'read', name='msg_state', create_type=False)

Chat

Represents a personal or group chat.

id: Integer, primary key
is_group: Boolean
created_at: DateTime

ChatUser

Many-to-many link table between Chat and User.

id: Integer, primary key
chat_id: ForeignKey(Chat.id)
user_id: ForeignKey(User.id)

Message

id: Integer, primary key
chat_id: ForeignKey(Chat.id)
sender_id: ForeignKey(User.id)
message_cont: Text
state: msg_state_enum
date_sent: DateTime

GroupMeta (if implemented)

Extra info for group chats:

chat_id: ForeignKey(Chat.id), primary key
name: String
description: String
image_route: String

🔧 Routes and Views

1. POST /send-message

Sends a message to either a personal or group chat.

  • Requires: chat-id-field, message-input

  • Optional: is-personal-chat-field to distinguish chat type

@view_config(route_name='send_message', request_method='POST', renderer='json')
def send_message(request):
    verify_session(request)
    chat_id = request.POST.get('chat-id-field')
    is_personal = request.POST.get('is-personal-chat-field') == 'true'
    content = request.POST.get('message-input')

    # Validate input, sanitize content, etc.
    msg = Message(
        chat_id=chat_id,
        sender_id=request.user.id,
        message_cont=content,
        state='sent',
        date_sent=datetime.utcnow()
    )
    DBSession.add(msg)
    DBSession.flush()
    return {'success': True}

2. GET /get-chat-messages/<chat_id>

Fetch messages from a personal chat.

@view_config(route_name='get_chat_messages', renderer='json')
def get_chat_messages(request):
    chat_id = request.matchdict['chat_id']
    verify_chat_access(request.user.id, chat_id)
    messages = DBSession.query(Message)...filter_by(chat_id=chat_id).order_by(Message.date_sent).all()
    return {'messages': serialize_messages(messages)}

3. GET /get-group-chat-messages/<chat_id>

Fetch messages from a group chat + metadata.

@view_config(route_name='get_group_chat_messages', renderer='json')
def get_group_chat_messages(request):
    chat_id = request.matchdict['chat_id']
    chat = DBSession.query(Chat).get(chat_id)
    if not chat or not chat.is_group:
        raise HTTPNotFound

    verify_chat_access(request.user.id, chat_id)

    messages = DBSession.query(Message)...filter_by(chat_id=chat_id).order_by(Message.date_sent).all()
    members = DBSession.query(User)...join(ChatUser).filter(ChatUser.chat_id == chat_id).all()

    return {
        'messages': serialize_group_messages(messages),
        'chat_name': chat.groupmeta.name,
        'description': chat.groupmeta.description,
        'image_route': chat.groupmeta.image_route,
        'creation_date': chat.created_at,
        'members': serialize_users(members)
    }

4. GET /search-global?q=...

Global user search (used by group creation modal)

@view_config(route_name='search_global', renderer='json')
def search_global(request):
    q = request.params.get('q', '').strip()
    if not q or len(q) < 2:
        return []
    users = DBSession.query(User)...filter(...ilike(f'%{q}%')).limit(10).all()
    return [serialize_user(u) for u in users]

5. POST /create-group-chat

Creates a new group with selected users.

@view_config(route_name='create_group_chat', request_method='POST')
def create_group_chat(request):
    user_ids = request.POST.getall('user_ids')
    if len(user_ids) < 2:
        return HTTPBadRequest

    chat = Chat(is_group=True, created_at=datetime.utcnow())
    DBSession.add(chat)
    DBSession.flush()

    for uid in user_ids + [request.user.id]:  # Include creator
        link = ChatUser(chat_id=chat.id, user_id=uid)
        DBSession.add(link)

    groupmeta = GroupMeta(
        chat_id=chat.id,
        name=request.POST.get('group_name') or "Unnamed Group",
        description=request.POST.get('group_description') or "",
        image_route=request.POST.get('group_image_route') or ""
    )
    DBSession.add(groupmeta)

    return HTTPFound(location=f'/chat?currentChatId={chat.id}')

6. POST /remove-user-from-group

Removes a user from a group. Optionally restrict to group admins.

@view_config(route_name='remove_user_from_group', request_method='POST', renderer='json')
def remove_user_from_group(request):
    group_id = request.POST.get('group_id')
    target_user_id = request.POST.get('user_id')

    # Check if requester is allowed
    verify_chat_access(request.user.id, group_id)

    DBSession.query(ChatUser).filter_by(chat_id=group_id, user_id=target_user_id).delete()
    return {'success': True}

📦 Supporting Utilities

verify_chat_access(user_id, chat_id)

Confirms user belongs to chat (used in all fetch/send routes).

serialize_messages(messages)

Returns message dictionaries with:

[
  {
    "message_cont": "hi",
    "sender_id": 3,
    "date_sent": "2025-07-02T13:45:00Z",
    "state": "sent"
  }
]

serialize_users(users)

Returns minimal public info for chat members.


🛡️ Auth / Session

Use verify_session(request) on routes to protect endpoints. This likely wraps cookie/session logic and sets request.user.


🧠 Notes

  • Message polling is client-side every ~3.5s, but the backend performs no heavy computation, so it scales acceptably for small/medium apps.

  • Use ETag or Last-Modified headers in future if backend cache validation is needed.

  • Add pagination or lazy-load if you expect message history to grow large.

Last updated