How do I create a chat scheme?

I am trying to implement a chat in my app and having difficulties with the scheme.

I wanted to add the following to the normal chat functionality:

There should be a group chat.
It should be shown who read the message and when.
If someone is typing this should be displayed.

My idea so far was the following scheme:

Class Chat
{groupName (String), author (pointer: User), members (relation: User), messages (relation: ChatMessages), isGroupChat (bool)}

The question I am asking myself is, should I set a relation to messages or is it better to just set a pointer in the ChatMessages class to Chat? `

Class ChatMessages
{chat (pointer: Chat), message (String), readBy (relation: ChatReadBy)}

Same question here. Set a pointer or simply a pointer / relation from Chat to ChatMessages?

Class ChatReadBy
{user (pointer: User), createdAt (Date)}

Then the question remains, where do I put isTyping?

And the most important question, does the scheme I have set up make sense at all?

Since many have probably already implemented a chat functionality, I hope that you can help me or give a few tips.

İn order parse server to notify clients, an object needs to be created or updated which is not necessary for live who is typing feature imo. But quick workaround for that would be, since parse server work with express, you can implement socket.io with your express app and use that for live who is typing feature without saving anything to database and for the rest you continue to use parse server.

Ok thanks, I also thought of socket.io. I thought there might be a function that I might have overlooked in Parse

So far I have tried the following scheme, but unfortunately I am not satisfied with it. There have to be a lot of cloud code queries to check the permissions.

Chat
{cretor (pointer: User) members (relation: User) isGroupChat (bool) groupName (String)}

ChatMessage

{author (pointer: User) message (String) offlineId (String) chat (pointer: Chat)}

I use the offlineId for GraphQL optimistic caching.

Does it make more sense to set a role for every chat? Are there disadvantages to assigning many roles?

Is there a possibility in cloud code to check whether the user is present in the relation members of chat? That’s how I’ve tried it so far.

Parse.Cloud.beforeFind("ChatMessage", async (req) => {
    if (req.master) return req.query;
    const query = req.query;
    const user = req.user;
    const publicUser = await findUserPublic(user.get("username"));
    const results = await query.find({useMasterKey: true});
    if (results === undefined || results.length === 0) return query;
    if (results[0].get("chat") !== undefined) {
        const chat = await results[0].get("chat").fetch({useMasterKey: true});
        const members = await chat.relation("members").query().find({useMasterKey: true});
        let isIn = false;
        for (const member of members) {
            if (member.id === publicUser.id) {
                isIn = true;
                break;
            }
        }
        if (!isIn) throw "Not allowed";
    } else {
        throw 'Nothing found';
    }
    return query;
}, {
    requireUser: true,
});

I have one more question, does include not work with GraphQL queries in cloud code?

This query returns results[0].get(“chat”) = undefined even if I add query.include(‘chat’)

query GetLastChatMessage($chatId: ID!) {
  chatMessages(
    where: { chat: { have: { objectId: { equalTo: $chatId } } } }
    last: 1
  ) {
    edges {
      node {
        message
        author {
          ...PublicUserFragmentCore
        }
      }
    }
  }
}

fragment PublicUserFragmentCore on UserPublic {
  __typename
  objectId
  photoURL
  username
}

But this works

query GetLastChatMessage($chatId: ID!) {
  chatMessages(
    where: { chat: { have: { objectId: { equalTo: $chatId } } } }
    last: 1
  ) {
    edges {
      node {
        message
        chat {
          objectId
        }
        author {
          ...PublicUserFragmentCore
        }
      }
    }
  }
}

fragment PublicUserFragmentCore on UserPublic {
  __typename
  objectId
  photoURL
  username
}

Ok I saw in the documentation that I can simplify the beforeFind function significantly with matchesQuery

Parse.Cloud.beforeFind("ChatMessage", async (req) => {
    if (req.master) return req.query;
    const query = req.query;
    const user = req.user;
    const publicUser = await findUserPublic(user.get("username"));
    const chatQuery = new Parse.Query('Chat');
    chatQuery.equalTo('members', publicUser);
    const messageQuery = new Parse.Query('ChatMessage');
    messageQuery.matchesQuery('chat', chatQuery);
    const newQuery = Parse.Query.and(query, messageQuery);
    return newQuery;
}, {
    requireUser: true,
});