-
-
Notifications
You must be signed in to change notification settings - Fork 723
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Updated chat schema #2466
base: develop
Are you sure you want to change the base?
Updated chat schema #2466
Conversation
…into chat-feature
…o reply-functionality
@@ -88,7 +88,7 @@ | |||
createComment(postId: ID!, data: CommentInput!): Comment @auth | |||
createDirectChat(data: createChatInput!): DirectChat! @auth | |||
createChat(data: chatInput!): Chat |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Removed all the references of DirectChat
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 8
Outside diff range and nitpick comments (3)
src/resolvers/Mutation/sendMessageToChat.ts (1)
1-73
: Add tests to cover the various scenarios.To ensure the reliability and maintainability of the
sendMessageToChat
function, it is crucial to have a comprehensive test suite. Consider adding tests to cover the following scenarios:
- Sending a message to a valid chat as a participant user.
- Attempting to send a message to a non-existent chat.
- Attempting to send a message as a non-existent user.
- Attempting to send a message to a chat where the user is not a participant.
- Verifying the subscription event is published with the correct data.
By covering these scenarios, you can have confidence in the function's behavior and catch any potential issues early in the development process.
src/models/ChatMessage.ts (2)
7-9
: Update documentation to reflect 'ChatMessage' instead of 'DirectChatMessage'.The description still refers to "direct chat message". Please update it to "chat message" to reflect the new unified structure.
69-76
: Remove redundant 'createdAt' and 'updatedAt' fields from the schema.Since
timestamps: true
is enabled, Mongoose automatically addscreatedAt
andupdatedAt
fields. Defining them explicitly is unnecessary.Apply this diff to remove the redundant fields:
deletedBy: [ { type: Schema.Types.ObjectId, ref: "User", required: false, }, ], - updatedAt: { - type: Date, - required: true, - }, - createdAt: { - type: Date, - required: true, - }, }, { timestamps: true, // Adds createdAt and updatedAt automatically },
Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Files selected for processing (17)
- schema.graphql (6 hunks)
- src/models/ChatMessage.ts (2 hunks)
- src/resolvers/Mutation/createMessageChat.ts (0 hunks)
- src/resolvers/Mutation/index.ts (4 hunks)
- src/resolvers/Mutation/sendMessageToChat.ts (1 hunks)
- src/resolvers/Subscription/directMessageChat.ts (0 hunks)
- src/resolvers/Subscription/index.ts (1 hunks)
- src/resolvers/Subscription/messageSentToChat.ts (1 hunks)
- src/typeDefs/mutations.ts (2 hunks)
- src/typeDefs/queries.ts (1 hunks)
- src/typeDefs/subscriptions.ts (1 hunks)
- src/typeDefs/types.ts (1 hunks)
- src/types/generatedGraphQLTypes.ts (20 hunks)
- tests/resolvers/Mutation/createMessageChat.spec.ts (0 hunks)
- tests/resolvers/Query/chatById.spec.ts (1 hunks)
- tests/resolvers/Query/chatsByuserId.spec.ts (1 hunks)
- tests/resolvers/Subscription/directMessageChat.spec.ts (0 hunks)
Files not reviewed due to no reviewable changes (4)
- src/resolvers/Mutation/createMessageChat.ts
- src/resolvers/Subscription/directMessageChat.ts
- tests/resolvers/Mutation/createMessageChat.spec.ts
- tests/resolvers/Subscription/directMessageChat.spec.ts
Files skipped from review as they are similar to previous changes (3)
- src/resolvers/Mutation/index.ts
- src/resolvers/Subscription/messageSentToChat.ts
- tests/resolvers/Query/chatById.spec.ts
Additional context used
Learnings (1)
src/types/generatedGraphQLTypes.ts (1)
<retrieved_learning>
Learnt from: disha1202
PR: #2466
File: src/types/generatedGraphQLTypes.ts:3096-3101
Timestamp: 2024-09-22T09:37:15.069Z
Learning: Direct chats do not need to be associated with any organization, so theorganizationId
field inChatInput
remains optional.
</retrieved_learning>
Additional comments not posted (22)
src/typeDefs/subscriptions.ts (1)
6-6
: Approve the consolidation of subscription fields.The introduction of the
messageSentToChat
field in theSubscription
type aligns with the objective of merging theDirectChat
andGroupChat
entities into a unifiedChat
entity. This change simplifies the message handling process and improves the overall chat architecture.Please ensure that the corresponding client-side code is updated to use the new subscription field and that existing applications relying on the removed fields (
messageSentToDirectChat
andmessageSentToGroupChat
) are migrated accordingly.Run the following script to verify the usage of the removed subscription fields in the codebase:
src/resolvers/Subscription/index.ts (2)
2-2
: LGTM! Ensure thorough testing of the consolidated subscription.The import statement for the
messageSentToChat
subscription is correct and aligns with the objective of mergingDirectChat
andGroupChat
entities into a unifiedChat
entity.Please ensure that the consolidated
messageSentToChat
subscription is thoroughly tested to confirm that it handles all chat-related functionality correctly, replacing the previously separate subscriptions.
5-5
: LGTM! Update the API documentation and communicate the change to the users.The export of the
messageSentToChat
subscription as part of theSubscription
object is correct and aligns with the objective of consolidating multiple subscriptions into a single subscription.Please ensure that the API documentation is updated to reflect the removal of the
directMessageChat
,messageSentToDirectChat
, andmessageSentToGroupChat
subscriptions and their replacement with themessageSentToChat
subscription. Also, communicate this change to the users of the API so that they can update their applications accordingly.tests/resolvers/Query/chatsByuserId.spec.ts (3)
6-8
: LGTM!The imports have been updated correctly to match the new resolver, model, and argument type names.
26-26
: LGTM!The test suite description has been updated correctly to match the new resolver name.
42-52
: LGTM!The test case has been updated correctly to use the new argument type, resolver, and model. The assertions have also been updated to ensure they are checking the correct payloads.
src/resolvers/Mutation/sendMessageToChat.ts (1)
1-73
: Excellent work on implementing thesendMessageToChat
function!The function handles the core functionality of sending a message to a chat effectively. It performs necessary validations, creates a new chat message, updates the chat document, and publishes a subscription event. The error handling and translations ensure a robust and user-friendly experience.
src/typeDefs/queries.ts (2)
47-47
: LGTM!The
chatById
query is a useful addition that aligns with the objective of consolidating the chat structure. It provides a way to retrieve a specific chat by its ID, replacing the previousgroupChatById
query.
49-49
: LGTM!The
chatsByUserId
query is a valuable addition that aligns with the objective of consolidating the chat structure. It provides a way to retrieve all chats associated with a specific user, replacing the previousdirectChatsByUserID
andgroupChatsByUserId
queries.src/typeDefs/mutations.ts (1)
235-240
: Mutation changes look good! Please provide more details on the new parameters.The modifications to the
sendMessageToChat
mutation align with the goal of unifying the chat functionality. The addition of thetype
andreplyTo
parameters enhances the flexibility of sending messages.A few questions to ensure clarity:
- What are the allowed values for the
type
parameter, and how is it being used to determine the chat type (DIRECT or GROUP)? Please ensure that the valid types are documented.- Are there any constraints on the
replyTo
parameter? For example, should it be validated that the referenced message belongs to the same chat?src/typeDefs/types.ts (2)
720-733
: Excellent work on introducing the unifiedChat
type!The new
Chat
type effectively consolidates the previously separateDirectChat
andGroupChat
types, simplifying the overall chat structure. The chosen fields provide a comprehensive representation of a chat entity, including metadata, participants, messages, and organizational context.The
isGroup
field is a smart addition, as it allows distinguishing between direct and group chats while maintaining a unified structure. The relationships withUser
,Organization
, andChatMessage
types ensure data consistency and enable efficient querying.This refactoring aligns perfectly with the PR objective and enhances the clarity and maintainability of the chat-related functionality in the schema.
735-744
: Great job on defining theChatMessage
type!The
ChatMessage
type perfectly complements theChat
type in unifying the chat structure. It encapsulates all the essential information related to a chat message, such as metadata, content, sender, and deletion status.The mandatory
chatMessageBelongsTo
field ensures referential integrity by enforcing a relationship with theChat
type. This guarantees that every message belongs to a specific chat, preventing orphaned messages.The
replyTo
field is a valuable addition, as it enables the functionality of message replies. This enhances the conversational structure and allows for more organized and contextual communication within chats.The
sender
field establishes a clear relationship with theUser
type, making it easy to identify the author of each message. ThedeletedBy
field provides the flexibility to track message deletions by multiple users, which can be useful for moderation and auditing purposes.Overall, the
ChatMessage
type is well-structured and aligns perfectly with the goals of the PR. It contributes to a cleaner and more intuitive chat-related schema.schema.graphql (8)
193-206
: LGTM!The
Chat
type looks good with appropriate fields to represent a chat entity. The inclusion ofadmins
field andisGroup
flag provides support for both direct and group chats. Themessages
field establishes the necessary relationship with theChatMessage
type.
208-217
: TheChatMessage
type structure looks good overall.The type includes appropriate fields to represent a chat message. The
chatMessageBelongsTo
field establishes the necessary relationship with theChat
type. ThedeletedBy
field provides a way to track message deletions.
1481-1481
: LGTM!The
chatById
query looks good. It accepts a requiredid
argument and returns the correspondingChat
type.
1482-1482
: LGTM!The
chatsByUserId
query looks good. It accepts a requiredid
argument representing the user ID and returns an array of associatedChat
types.
1085-1085
: LGTM!The
createChat
mutation looks good. It accepts a requireddata
argument of typechatInput
to capture the necessary details for creating a new chat. It returns the createdChat
type.
1618-1618
: LGTM!The
messageSentToChat
subscription looks good. It allows subscribing to new messages sent to a chat for a specific user. The@auth
directive ensures that authentication is required to establish the subscription.
214-214
: Avoid recursive type inreplyTo
field to prevent excessive nesting.Setting
replyTo
asChatMessage
can lead to deep recursive nesting when querying messages, which may cause performance issues. Consider usingID
as the type forreplyTo
to reference the parent message without nesting.Apply this diff to change the
replyTo
field type:type ChatMessage { _id: ID! chatMessageBelongsTo: Chat! createdAt: DateTime! deletedBy: [User] messageContent: String! - replyTo: ChatMessage + replyTo: ID sender: User! updatedAt: DateTime! }Likely invalid or redundant comment.
2035-2038
: IncludeuserIds
as a required field inchatInput
for completeness.The
userIds
field in thechatInput
type is currently optional. To ensure that participants are always specified when creating a new chat, consider making it a required field.Apply this diff:
input chatInput { image: String isGroup: Boolean! name: String + organizationId: ID + userIds: [ID!]! }Likely invalid or redundant comment.
src/types/generatedGraphQLTypes.ts (2)
13-14
: ImportingInterfaceChatModel
andInterfaceChatMessageModel
The new imports for
InterfaceChatModel
andInterfaceChatMessageModel
are correctly added. This ensures the necessary models are available for the updated types.
3096-3101
: Confirmation: OptionalorganizationId
inChatInput
is appropriateThe
ChatInput
type correctly keepsorganizationId
as optional. This aligns with the requirement that direct chats do not need to be associated with an organization.
const args: QueryChatsByUserIdArgs = { | ||
id: new Types.ObjectId().toString(), | ||
}; | ||
|
||
await directChatsByUserIDResolver?.({}, args, {}); | ||
await chatsByUserIdResolver?.({}, args, {}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Update error message for consistency.
The error message in the test case should reflect the updated chat model. Consider updating "DirectChats not found" to a more generic message.
} catch (error: unknown) {
- expect((error as Error).message).toEqual("DirectChats not found");
+ expect((error as Error).message).toEqual("Chats not found");
}
Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const args: QueryChatsByUserIdArgs = { | |
id: new Types.ObjectId().toString(), | |
}; | |
await directChatsByUserIDResolver?.({}, args, {}); | |
await chatsByUserIdResolver?.({}, args, {}); | |
const args: QueryChatsByUserIdArgs = { | |
id: new Types.ObjectId().toString(), | |
}; | |
await chatsByUserIdResolver?.({}, args, {}); |
const chat = await Chat.findOne({ | ||
_id: args.chatId, | ||
}).lean(); | ||
|
||
if (!chat) { | ||
throw new errors.NotFoundError( | ||
requestContext.translate(CHAT_NOT_FOUND_ERROR.MESSAGE), | ||
CHAT_NOT_FOUND_ERROR.CODE, | ||
CHAT_NOT_FOUND_ERROR.PARAM, | ||
); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Consider adding a check to verify if the user is a participant of the chat.
To enhance security and prevent unauthorized users from sending messages to a chat, it would be beneficial to verify if the user sending the message (context.userId
) is actually a participant of the specified chat (args.chatId
).
You can add this check after the chat existence validation:
// Check if the user is a participant of the chat
const isParticipant = chat.participants.includes(context.userId);
if (!isParticipant) {
throw new errors.ForbiddenError(
requestContext.translate(USER_NOT_PARTICIPANT_ERROR.MESSAGE),
USER_NOT_PARTICIPANT_ERROR.CODE,
USER_NOT_PARTICIPANT_ERROR.PARAM,
);
}
This ensures that only users who are participants of the chat can send messages to it.
@@ -88,7 +84,7 @@ export const mutations = gql` | |||
createComment(postId: ID!, data: CommentInput!): Comment @auth | |||
createDirectChat(data: createChatInput!): DirectChat! @auth | |||
createChat(data: chatInput!): Chat |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tip
Codebase Verification
Incomplete removal of createDirectChat
and DirectChat
found.
The removal of createDirectChat
and DirectChat
was not fully completed. Please address the following:
-
Resolvers:
src/resolvers/index.ts
src/resolvers/Mutation/sendMessageToChat.ts
-
Type Definitions:
src/typeDefs/errors/index.ts
src/typeDefs/errors/createDirectChatError.ts
src/types/generatedGraphQLTypes.ts
-
Models:
src/models/Chat.ts
src/models/ChatMessage.ts
-
Tests:
tests/resolvers/ChatMessage/chatMessageBelongsTo.spec.ts
tests/resolvers/ChatMessage/sender.spec.ts
tests/resolvers/Subscription/messageSentToChat.spec.ts
tests/resolvers/Query/chatsByuserId.spec.ts
tests/resolvers/ChatMessage/replyTo.spec.ts
tests/resolvers/Chat/creator.spec.ts
tests/resolvers/Mutation/sendMessageToChat.spec.ts
Ensure that all references to createDirectChat
and DirectChat
are removed or updated to use the new createChat
mutation and Chat
type to maintain codebase consistency.
Analysis chain
Mutation looks good! Verify the removal of createDirectChat
and DirectChat
from the codebase.
The addition of the createChat
mutation aligns with the objective of consolidating chat types. Please ensure that:
- The
chatInput
input type covers all necessary fields for creating a chat. - All occurrences of the
createDirectChat
mutation andDirectChat
type have been removed or updated to usecreateChat
andChat
respectively.
Run the following script to verify the removal of createDirectChat
and DirectChat
:
Scripts executed
The following scripts were executed for the analysis:
Script:
#!/bin/bash
# Description: Verify the removal of the old `createDirectChat` mutation and `DirectChat` type.
# Test 1: Search for the `createDirectChat` mutation. Expect: No results.
rg --type ts 'createDirectChat'
# Test 2: Search for the `DirectChat` type. Expect: No results.
rg --type ts 'DirectChat'
Length of output: 2815
@@ -1165,8 +1151,7 @@ type Mutation { | |||
revokeRefreshTokenForUser: Boolean! | |||
saveFcmToken(token: String): Boolean! | |||
sendMembershipRequest(organizationId: ID!): MembershipRequest! | |||
sendMessageToDirectChat(chatId: ID!, messageContent: String!): DirectChatMessage! | |||
sendMessageToGroupChat(chatId: ID!, messageContent: String!): GroupChatMessage! | |||
sendMessageToChat(chatId: ID!, messageContent: String!, replyTo: ID, type: String!): ChatMessage! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use MessageType
enum for type
parameter in sendMessageToChat
mutation.
Replacing the String
type with the MessageType
enum for the type
parameter enhances validation and ensures only predefined message types are accepted.
Define the MessageType
enum:
enum MessageType {
TEXT
IMAGE
VIDEO
FILE
}
Apply this diff:
type Mutation {
- sendMessageToChat(chatId: ID!, messageContent: String!, replyTo: ID, type: String!): ChatMessage!
+ sendMessageToChat(chatId: ID!, messageContent: String!, replyTo: ID, type: MessageType!): ChatMessage!
...
}
src/models/ChatMessage.ts
Outdated
* @param chatMessageBelongsTo - Reference to the chat session to which the message belongs. | ||
* @param sender - Reference to the user who sent the message. | ||
* @param receiver - Reference to the user who received the message. | ||
* @param messageContent - Content of the direct chat message. | ||
* @param replyTo - Reference to the message replied to. | ||
* @param type - Type of the message (STRING, MEDIA, FILE). | ||
* @param status - Status of the message (ACTIVE, BLOCKED, DELETED). | ||
* @param deletedBy - List of users who have deleted the message. | ||
* @param createdAt - Date when the direct chat message was created. | ||
* @param updatedAt - Date when the direct chat message was last updated. | ||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Synchronize JSDoc comments with the current schema.
Some @param
tags reference properties that are either renamed or no longer exist (e.g., type
, messageContent
described as "direct chat message"). Please update the documentation to match the updated property names and descriptions.
sender: PopulatedDoc<InterfaceUser & Document>; | ||
receiver: PopulatedDoc<InterfaceUser & Document>; | ||
replyTo: PopulatedDoc<InterfaceChatMessage & Document>; | ||
messageContent: string; | ||
status: string; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Define 'status' as a TypeScript enum for better type safety.
Currently, status
is typed as a string. Defining it using a TypeScript enum enhances type safety and ensures only valid status values are assigned.
Example:
export enum ChatMessageStatus {
ACTIVE = "ACTIVE",
BLOCKED = "BLOCKED",
DELETED = "DELETED",
}
export interface InterfaceChatMessage {
// ...
status: ChatMessageStatus;
// ...
}
Update the schema to reflect the enum:
status: {
type: String,
required: true,
- enum: ["ACTIVE", "BLOCKED", "DELETED"],
+ enum: Object.values(ChatMessageStatus),
default: ChatMessageStatus.ACTIVE,
},
export type Chat = { | ||
__typename?: 'Chat'; | ||
_id: Scalars['ID']['output']; | ||
admins?: Maybe<Array<Maybe<User>>>; | ||
createdAt: Scalars['DateTime']['output']; | ||
creator?: Maybe<User>; | ||
image?: Maybe<Scalars['String']['output']>; | ||
isGroup: Scalars['Boolean']['output']; | ||
lastMessageId?: Maybe<Scalars['String']['output']>; | ||
messages?: Maybe<Array<Maybe<ChatMessage>>>; | ||
name?: Maybe<Scalars['String']['output']>; | ||
organization?: Maybe<Organization>; | ||
updatedAt: Scalars['DateTime']['output']; | ||
users: Array<User>; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Refine the type of lastMessageId
in Chat
The Chat
type is well-defined. However, the lastMessageId
field is currently of type Scalars['String']['output']
, which may not accurately represent an ID. For consistency and clarity, consider changing its type to Scalars['ID']['output']
, as it likely references a ChatMessage
ID.
Apply this diff to update the type of lastMessageId
:
export type Chat = {
__typename?: 'Chat';
_id: Scalars['ID']['output'];
admins?: Maybe<Array<Maybe<User>>>;
createdAt: Scalars['DateTime']['output'];
creator?: Maybe<User>;
image?: Maybe<Scalars['String']['output']>;
isGroup: Scalars['Boolean']['output'];
- lastMessageId?: Maybe<Scalars['String']['output']>;
+ lastMessageId?: Maybe<Scalars['ID']['output']>;
messages?: Maybe<Array<Maybe<ChatMessage>>>;
name?: Maybe<Scalars['String']['output']>;
organization?: Maybe<Organization>;
updatedAt: Scalars['DateTime']['output'];
users: Array<User>;
};
Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
export type Chat = { | |
__typename?: 'Chat'; | |
_id: Scalars['ID']['output']; | |
admins?: Maybe<Array<Maybe<User>>>; | |
createdAt: Scalars['DateTime']['output']; | |
creator?: Maybe<User>; | |
image?: Maybe<Scalars['String']['output']>; | |
isGroup: Scalars['Boolean']['output']; | |
lastMessageId?: Maybe<Scalars['String']['output']>; | |
messages?: Maybe<Array<Maybe<ChatMessage>>>; | |
name?: Maybe<Scalars['String']['output']>; | |
organization?: Maybe<Organization>; | |
updatedAt: Scalars['DateTime']['output']; | |
users: Array<User>; | |
}; | |
export type Chat = { | |
__typename?: 'Chat'; | |
_id: Scalars['ID']['output']; | |
admins?: Maybe<Array<Maybe<User>>>; | |
createdAt: Scalars['DateTime']['output']; | |
creator?: Maybe<User>; | |
image?: Maybe<Scalars['String']['output']>; | |
isGroup: Scalars['Boolean']['output']; | |
lastMessageId?: Maybe<Scalars['ID']['output']>; | |
messages?: Maybe<Array<Maybe<ChatMessage>>>; | |
name?: Maybe<Scalars['String']['output']>; | |
organization?: Maybe<Organization>; | |
updatedAt: Scalars['DateTime']['output']; | |
users: Array<User>; | |
}; |
export type ChatMessage = { | ||
__typename?: 'ChatMessage'; | ||
_id: Scalars['ID']['output']; | ||
chatMessageBelongsTo: Chat; | ||
createdAt: Scalars['DateTime']['output']; | ||
deletedBy?: Maybe<Array<Maybe<User>>>; | ||
messageContent: Scalars['String']['output']; | ||
replyTo?: Maybe<ChatMessage>; | ||
sender: User; | ||
updatedAt: Scalars['DateTime']['output']; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rename chatMessageBelongsTo
to chat
for clarity
In the ChatMessage
type, the field chatMessageBelongsTo
could be renamed to chat
to improve readability and align with common naming conventions. This makes it immediately clear that the message is associated with a Chat
.
Apply this diff to rename the field:
export type ChatMessage = {
__typename?: 'ChatMessage';
_id: Scalars['ID']['output'];
- chatMessageBelongsTo: Chat;
+ chat: Chat;
createdAt: Scalars['DateTime']['output'];
deletedBy?: Maybe<Array<Maybe<User>>>;
messageContent: Scalars['String']['output'];
replyTo?: Maybe<ChatMessage>;
sender: User;
updatedAt: Scalars['DateTime']['output'];
};
Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
export type ChatMessage = { | |
__typename?: 'ChatMessage'; | |
_id: Scalars['ID']['output']; | |
chatMessageBelongsTo: Chat; | |
createdAt: Scalars['DateTime']['output']; | |
deletedBy?: Maybe<Array<Maybe<User>>>; | |
messageContent: Scalars['String']['output']; | |
replyTo?: Maybe<ChatMessage>; | |
sender: User; | |
updatedAt: Scalars['DateTime']['output']; | |
}; | |
export type ChatMessage = { | |
__typename?: 'ChatMessage'; | |
_id: Scalars['ID']['output']; | |
chat: Chat; | |
createdAt: Scalars['DateTime']['output']; | |
deletedBy?: Maybe<Array<Maybe<User>>>; | |
messageContent: Scalars['String']['output']; | |
replyTo?: Maybe<ChatMessage>; | |
sender: User; | |
updatedAt: Scalars['DateTime']['output']; | |
}; |
let testChatMessageWithoutReply: TestChatMessageType; | ||
let MONGOOSE_INSTANCE: typeof mongoose; | ||
|
||
beforeAll(async () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The beforeAll
block asynchronously sets up data. However, if one of the await calls (e.g., createTestChatMessage
) fails, you won’t know which one because the error handling isn’t granular. Use try-catch around each await call for better error handling and debugging.
Example
try {
MONGOOSE_INSTANCE = await connect();
} catch (error) {
console.error("Failed to connect to the database", error);
throw error;
}
try {
const temp = await createTestChatMessageWithoutReply();
testChatMessageWithoutReply = temp[3];
} catch (error) {
console.error("Failed to create test chat message without reply", error);
throw error;
}
testChatMessage = temp1[3]; | ||
}); | ||
|
||
afterAll(async () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If connect()
or data creation fails in beforeAll
, afterAll
could still try to run disconnect()
, which might throw errors since MONGOOSE_INSTANCE
could be undefined. Add a conditional check before calling disconnect.
Example
afterAll(async () => {
if (MONGOOSE_INSTANCE) {
await disconnect(MONGOOSE_INSTANCE);
}
});
|
||
describe("resolvers -> DirectChatMessage -> replyTo", () => { | ||
it(`returns directChat object for parent.replyTo`, async () => { | ||
const parent = testChatMessage?.toObject(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using ?.
before toObject()
could mask problems, especially if testChatMessage
or testChatMessageWithoutReply
is null
. In the case of null objects, you’ll just get undefined
, leading to silent failures. Use a check with if (!parent)
before calling toObject()
.
const parent = testChatMessage ? testChatMessage.toObject() : null;
if (!parent) {
throw new Error("Parent object is undefined.");
}
}); | ||
|
||
describe("resolvers -> DirectChatMessage -> replyTo", () => { | ||
it(`returns directChat object for parent.replyTo`, async () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If replyToResolver
or ChatMessage.findOne
fail in the first test, it may cause errors that aren’t caught. Add explicit try-catch blocks around async calls within your test cases, especially for the first test.
it(`returns directChat object for parent.replyTo`, async () => {
const parent = testChatMessage?.toObject();
if (!parent) throw new Error("Parent object is undefined.");
if (typeof replyToResolver !== "function") throw new Error("replyToResolver is not a function.");
try {
const replyToPayload = await replyToResolver(parent, {}, {});
const replyTo = await ChatMessage.findOne({ _id: testChatMessage?.replyTo }).lean();
expect(replyToPayload).toEqual(replyTo);
} catch (error) {
console.error("Test failed", error);
throw error;
}
});
|
||
expect(replyToPayload).toEqual(replyTo); | ||
}); | ||
it(`throws NotFoundError if no directChat exists`, async () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The spy for requestContext.translate
could be mocking more than necessary, potentially causing unexpected side effects. Ensure you restore the original function after the test to avoid interference in subsequent tests.
it(`throws NotFoundError if no directChat exists`, async () => {
const { requestContext } = await import("../../../src/libraries");
const spy = vi.spyOn(requestContext, "translate").mockImplementationOnce((message: string) => message);
try {
const parent = { ...testChatMessage?.toObject(), replyTo: new Types.ObjectId() };
await replyToResolver(parent, {}, {});
} catch (error) {
expect(spy).toBeCalledWith(MESSAGE_NOT_FOUND_ERROR.MESSAGE);
expect((error as Error).message).toEqual(MESSAGE_NOT_FOUND_ERROR.MESSAGE);
} finally {
spy.mockRestore(); // Restore the original function
}
});
} | ||
|
||
try { | ||
// @ts-expect-error - Testing for error |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The @ts-expect-error
comment assumes a TypeScript error should happen. Ensure the expected error is related to the types you are actually testing. Add explicit type validation to ensure errors occur for the expected reason.
import { createTestUserAndOrganization } from "./userAndOrg"; | ||
import type { Document } from "mongoose"; | ||
|
||
export type TestChatType = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The TestChatType
and TestChatMessageType
types use union types that include null
. This can introduce potential null checks across your codebase, increasing verbosity and potential runtime errors if not handled properly. Use utility types like Partial
or Optional
depending on your specific use case, or ensure that null is only used where necessary. You might also want to avoid unions if you can guarantee non-null values at certain points in your logic.
export type TestChatType = InterfaceChat & Document<unknown, unknown, InterfaceChat>;
export type TestChatMessageType = InterfaceChatMessage & Document<unknown, unknown, InterfaceChatMessage>;
If you need to handle null, then you can explicitly check for it at points of use.
const someChat: TestChatType = null; // Ensure you handle null later in the code
if (someChat === null) {
// handle null case
} else {
// process chat
}
src/resolvers/Mutation/createChat.ts
Outdated
} | ||
} | ||
|
||
for await (const userId of args.data.userIds) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The loop uses for await
to check user existence one by one. This results in sequential database queries, which can be slow. Instead of checking each user individually, perform a bulk query to check all user IDs at once. This will significantly improve performance by reducing multiple database round-trips.
const userExists = await User.exists({
_id: { $in: args.data.userIds },
});
if (userExists.length !== args.data.userIds.length) {
// Find which user ID(s) do not exist
const missingUsers = args.data.userIds.filter(
(id) => !userExists.includes(id)
);
throw new errors.NotFoundError(
requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE),
USER_NOT_FOUND_ERROR.CODE,
{ missingUsers }
);
}
src/resolvers/Mutation/createChat.ts
Outdated
|
||
organization = organizationFoundInCache[0]; | ||
|
||
if (organizationFoundInCache.includes(null)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
organizationFoundInCache.includes(null)
is redundant because we’re only interested in the first organization (organizationFoundInCache[0])
. It could lead to confusion if you were checking multiple organizations, but in this case, it's unnecessary. Directly check the first result from the cache.
src/resolvers/Mutation/createChat.ts
Outdated
|
||
// Checks whether user with _id === userId exists. | ||
if (userExists === false) { | ||
throw new errors.NotFoundError( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error handling for the organization and user not being found could be more informative. Currently, it throws a generic error using requestContext.translate
but lacks detail about which organization or user was not found. Include more detailed information in the error, such as the organizationId
or userId
that was not found. This helps in debugging and error tracking.
src/resolvers/Mutation/createChat.ts
Outdated
isGroup: args.data.isGroup, | ||
creatorId: context.userId, | ||
users: usersInChat, | ||
organization: args.data?.organizationId, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using args.data?.organizationId
may be unnecessary because you already check whether the organizationId
exists before reaching this point in the code. Since the existence of organizationId is already validated, the optional chaining can be removed, improving clarity and eliminating ambiguity.
src/resolvers/Mutation/createChat.ts
Outdated
|
||
const createdChat = await Chat.create(chatPayload); | ||
|
||
return createdChat.toObject(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You’re calling .toObject()
at the end to convert the Mongoose document into a plain JavaScript object, which may be unnecessary depending on how you intend to use the created chat. If the client or further logic does not require a plain object, you can return the document directly or use .lean()
to avoid creating unnecessary copies.
tests/resolvers/Chat/admins.spec.ts
Outdated
throw new Error("Parent object is undefined."); | ||
} | ||
|
||
const usersPayload = await adminsResolver?.(parent, {}, {}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The adminsResolver?.(parent, {}, {})
uses optional chaining (?.
), which is unnecessary if you're confident that the adminsResolver
will always be defined. If you want to safeguard against undefined resolvers, it’s better to throw an error early on:
if (!adminsResolver) {
throw new Error("adminsResolver is not defined.");
}
const usersPayload = await adminsResolver(parent, {}, {});
tests/resolvers/Chat/admins.spec.ts
Outdated
beforeAll(async () => { | ||
MONGOOSE_INSTANCE = await connect(); | ||
const userOrgChat = await createTestChatMessage(); | ||
testChat = userOrgChat[2]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You access testChat
using userOrgChat[2]
. If the structure of userOrgChat
changes or the function that generates it is modified, this can lead to bugs. Use destructuring to make your code more robust and readable.
const [, , testChat] = await createTestChatMessage();
tests/resolvers/Chat/admins.spec.ts
Outdated
}); | ||
|
||
describe("resolvers -> Chat -> admins", () => { | ||
it(`returns user object for parent.users`, async () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test description (returns user object for parent.users
) might not be specific enough to describe the exact behavior under test. Update the description to reflect what exactly you are testing (e.g., that the adminsResolver
is correctly resolving admin users).
it(`resolves the correct admin users for the given chat`, async () => {
const parent = testChat?.toObject();
if (!parent) {
throw new Error("Parent object is undefined.");
}
const usersPayload = await adminsResolver(parent, {}, {});
const users = await User.find({
_id: { $in: testChat?.admins },
}).lean();
expect(usersPayload).toEqual(users);
});
|
||
const organizationPayload = await organizationResolver?.(parent, {}, {}); | ||
|
||
const organization = await Organization.findOne({ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the Organization.findOne()
query, you're using $in
with a single organization ID. However, $in
is used for matching multiple items. Since testChat.organization
should be a single ID, use direct matching instead of $in
.
const organization = await Organization.findOne({
_id: testChat?.organization,
}).lean();
}); | ||
|
||
describe("resolvers -> Chat -> organization", () => { | ||
it(`returns user object for parent.users`, async () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test description (returns user object for parent.users
) is not accurate. You're testing the organization resolver, not the user resolver. Update the test description to properly reflect the behavior you're testing:
it("resolves the correct organization for the given chat", async () => {
const parent = testChat?.toObject();
if (!parent) {
throw new Error("Parent object is undefined.");
}
const organizationPayload = await organizationResolver(parent, {}, {});
const organization = await Organization.findOne({
_id: testChat?.organization,
}).lean();
expect(organizationPayload).toEqual(organization);
});
|
||
expect(organizationPayload).toEqual(organization); | ||
}); | ||
it(`returns user objects for parent.organization from cache`, async () => { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The second test seems to be checking whether the organization can be resolved from the cache, but there is no logic in place to actually use or verify a cache mechanism. If you’re testing a cached organization resolution, you should mock or inject a cache service to validate whether the cache is being used. Additionally, the description and logic of the test should match:
it("resolves the organization from cache", async () => {
const parent = testChat?.toObject();
if (!parent) {
throw new Error("Parent object is undefined.");
}
// Simulate the cache resolution logic here (if applicable)
const organizationPayload = await organizationResolver(parent, {}, {});
const organization = await Organization.findOne({
_id: testChat?.organization,
}).lean();
expect(organizationPayload).toEqual(organization);
});
If you have a specific cache service (e.g., findOrganizationsInCache
), make sure to mock its behavior and assert whether it's being called.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 7
🧹 Outside diff range and nitpick comments (2)
src/resolvers/Mutation/createChat.ts (2)
10-10
: Correct the grammatical error in the function descriptionThe sentence "This function enables to create a chat." is grammatically incorrect. Consider rephrasing it to "This function enables the creation of a chat." or "This function allows creating a chat."
10-18
: Update function documentation for accuracyIn the
@remarks
section, point 1 states: "1. If the user exists". Since the function checks for multiple users, consider updating it to "1. If the users exist" for clarity and accuracy.
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
📒 Files selected for processing (7)
- src/models/ChatMessage.ts (1 hunks)
- src/resolvers/Mutation/createChat.ts (1 hunks)
- tests/helpers/chat.ts (1 hunks)
- tests/resolvers/Chat/admins.spec.ts (1 hunks)
- tests/resolvers/Chat/organization.spec.ts (1 hunks)
- tests/resolvers/ChatMessage/replyTo.spec.ts (1 hunks)
- tests/resolvers/Query/chatsByuserId.spec.ts (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- tests/helpers/chat.ts
- tests/resolvers/ChatMessage/replyTo.spec.ts
🔇 Additional comments (14)
tests/resolvers/Chat/admins.spec.ts (4)
1-11
: LGTM: Imports and variable declarations are well-structured.The imports cover all necessary modules and types for the test file. The global variables
testChat
andMONGOOSE_INSTANCE
are appropriately declared outside the test scope.
13-20
: LGTM: Setup and teardown functions are well-implemented.The
beforeAll
andafterAll
functions correctly handle database connection and disconnection. The use of array destructuring in line 15 ([, , testChat] = await createTestChatMessage();
) is a good improvement, making the code more robust and readable as suggested in a previous review.
22-42
: LGTM: Test case is well-structured and addresses previous review comments.The test case effectively validates the
adminsResolver
functionality. Notable improvements include:
- A more specific test description that clearly states the test's purpose.
- Proper null checks for both the parent object and the resolver, replacing the previous optional chaining.
- Clear comparison between the resolver's output and the expected user data from the database.
These changes address the concerns raised in previous reviews and enhance the overall quality of the test.
1-42
: Great job on implementing this test file!The
admins.spec.ts
file is well-structured and effectively tests theadmins
resolver functionality. It follows testing best practices, including proper setup and teardown procedures, clear test descriptions, and thorough assertions. The improvements made based on previous review comments have significantly enhanced the code quality and readability.Some key strengths of this implementation:
- Proper database connection handling
- Effective use of array destructuring for test data setup
- Null checks to prevent potential runtime errors
- Clear and specific test descriptions
- Accurate comparison between resolver output and expected data
Keep up the good work in maintaining high-quality test coverage for the project!
tests/resolvers/Chat/organization.spec.ts (4)
2-2
: LGTM: Import changes reflect the new Chat structure.The updated imports correctly align with the shift from DirectChat to Chat in the resolver and test setup. The addition of TestChatType and createTestChatMessage imports supports the new test structure.
Also applies to: 6-8
10-10
: LGTM: Variable declarations updated for new Chat structure.The changes from 'testDirectChat' to 'testChat' and the use of 'createTestChatMessage' in the test setup are consistent with the new Chat structure. These modifications appropriately reflect the consolidation of chat types in the main codebase.
Also applies to: 15-16
23-25
: LGTM: First test case updated correctly.The changes in the first test case accurately reflect the new Chat structure:
- The test description now correctly describes the test's purpose.
- The use of 'testChat' instead of 'testDirectChat' is consistent throughout the test.
- The organization ID is correctly fetched from 'testChat'.
These changes align well with the consolidation of chat types in the main codebase.
Also applies to: 33-33
Line range hint
1-53
: LGTM: File structure and consistency align with new Chat entity.The overall structure and consistency of the file have been successfully updated to reflect the new Chat entity:
- All references to DirectChat have been replaced with Chat.
- Test cases have been modified to use the new Chat-related functions and variables.
- The flow of the tests remains logical and similar to the previous version.
- These changes align well with the PR objectives of consolidating DirectChat and GroupChat into a single Chat entity.
The modifications ensure that the tests now correctly focus on the Chat organization resolver, maintaining the integrity of the test suite while adapting to the new chat structure.
tests/resolvers/Query/chatsByuserId.spec.ts (1)
26-38
: LGTM: Error handling test case is well-implemented.This test case correctly checks for the "Chats not found" error when no chats exist for a given user ID. The error message has been appropriately updated to reflect the new unified chat model.
src/models/ChatMessage.ts (4)
16-16
: Define 'status' as a TypeScript enum for better type safety.Currently,
status
is typed as a string. Defining it using a TypeScript enum enhances type safety and ensures only valid status values are assigned.
99-99
: Update logging middleware schema name.The middleware should reference "ChatMessage" instead of "DirectChatMessage".
101-109
: LGTM: Model creation and export.The implementation for creating and exporting the ChatMessage model is well-structured. It correctly handles potential OverwriteModelError issues that could occur during testing, which is a good practice.
1-109
: Overall assessment: Good implementation with minor improvements needed.The ChatMessage model is well-structured and implements the necessary functionality. The identified issues are minor and mainly relate to documentation and naming consistency. Once these are addressed, the implementation will be robust and maintainable.
Key points:
- Consider using an enum for the 'status' field.
- Update JSDoc comments to accurately reflect the current schema.
- Update the logging middleware to reference "ChatMessage".
Great job on implementing this unified chat message model!
src/resolvers/Mutation/createChat.ts (1)
33-39
: IncludeorganizationId
in the error message for better debuggingWhen throwing the
NotFoundError
due to a missing organization, including theorganizationId
that was not found can aid in debugging and error tracking. It provides clear insight into which organization ID caused the error.
it("resolves the organization from cache", async () => { | ||
const parent = testChat?.toObject(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Implement cache simulation for more robust testing.
The changes in the second test case are mostly good:
- The test description accurately reflects the purpose of testing organization resolution from cache.
- The use of 'testChat' is consistent with the new Chat structure.
However, there's an opportunity for improvement:
The comment on line 44 mentions simulating cache resolution logic, but no actual cache simulation is implemented. To make this test more robust and meaningful:
- Implement a mock cache service or use a testing library to simulate cache behavior.
- Verify that the cache is being used by the resolver before falling back to database queries.
Here's a suggested implementation:
it("resolves the organization from cache", async () => {
const parent = testChat?.toObject();
if (!parent) {
throw new Error("Parent object is undefined.");
}
// Mock cache service
const mockCacheService = {
get: vi.fn().mockResolvedValue(null), // Simulate cache miss
set: vi.fn(),
};
// Pass mock cache service to the resolver
const organizationPayload = await organizationResolver?.(parent, {}, { cacheService: mockCacheService });
// Verify cache was checked
expect(mockCacheService.get).toHaveBeenCalledWith(`organization_${testChat?.organization}`);
// Verify result
const organization = await Organization.findOne({
_id: testChat?.organization,
}).lean();
expect(organizationPayload).toEqual(organization);
// Verify cache was updated
expect(mockCacheService.set).toHaveBeenCalledWith(`organization_${testChat?.organization}`, organization);
});
This implementation will provide a more thorough test of the caching mechanism in the resolver.
Also applies to: 44-44, 48-48
it(`returns list of all chats with chat.users containing the user | ||
with _id === args.id`, async () => { | ||
const args: QueryChatsByUserIdArgs = { | ||
id: testUser?._id, | ||
}; | ||
|
||
const chatsByUserIdPayload = await chatsByUserIdResolver?.({}, args, {}); | ||
|
||
const chatsByUserId = await Chat.find({ | ||
users: testUser?._id, | ||
}).lean(); | ||
|
||
expect(chatsByUserIdPayload).toEqual(chatsByUserId); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider enhancing the test case with more specific assertions.
While the current test correctly verifies that the resolver returns the expected chats, it could be more robust by checking specific properties of the returned chats. This would ensure that the structure of the returned data matches expectations.
Consider adding assertions to check specific properties of the returned chats. For example:
expect(chatsByUserIdPayload).toEqual(expect.arrayContaining([
expect.objectContaining({
_id: expect.any(Types.ObjectId),
users: expect.arrayContaining([expect.any(Types.ObjectId)]),
messages: expect.arrayContaining([expect.any(Types.ObjectId)]),
// Add other expected properties
})
]));
This would provide a more comprehensive test of the resolver's output structure.
describe("resolvers -> Query -> chatsByUserId", () => { | ||
it(`throws NotFoundError if no Chats exists with chats.users | ||
containing user with _id === args.id`, async () => { | ||
try { | ||
const args: QueryChatsByUserIdArgs = { | ||
id: new Types.ObjectId().toString(), | ||
}; | ||
|
||
await chatsByUserIdResolver?.({}, args, {}); | ||
} catch (error: unknown) { | ||
expect((error as Error).message).toEqual("Chats not found"); | ||
} | ||
}); | ||
|
||
it(`returns list of all chats with chat.users containing the user | ||
with _id === args.id`, async () => { | ||
const args: QueryChatsByUserIdArgs = { | ||
id: testUser?._id, | ||
}; | ||
|
||
const chatsByUserIdPayload = await chatsByUserIdResolver?.({}, args, {}); | ||
|
||
const chatsByUserId = await Chat.find({ | ||
users: testUser?._id, | ||
}).lean(); | ||
|
||
expect(chatsByUserIdPayload).toEqual(chatsByUserId); | ||
}); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider adding more test cases for comprehensive coverage.
While the current test file covers the basic error and success scenarios, consider adding more test cases to ensure comprehensive coverage of the chatsByUserId
resolver. Some suggestions:
- Test with a user who has no chats (different from the "not found" case).
- Test with a user who has multiple chats.
- Test with invalid user IDs (e.g., malformed ObjectId).
- Test pagination if applicable to this resolver.
Here's an example of an additional test case you could add:
it('returns an empty array for a user with no chats', async () => {
const userWithNoChats = await createTestUser(); // Assume this helper exists
const args: QueryChatsByUserIdArgs = {
id: userWithNoChats._id,
};
const chatsByUserIdPayload = await chatsByUserIdResolver?.({}, args, {});
expect(chatsByUserIdPayload).toEqual([]);
});
Adding these cases would provide more confidence in the resolver's behavior across various scenarios.
import type { TestUserType } from "../../helpers/userAndOrg"; | ||
|
||
let testUser: TestUserType; | ||
let MONGOOSE_INSTANCE: typeof mongoose; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Consider using a more specific type for MONGOOSE_INSTANCE
.
Instead of using typeof mongoose
, which is quite broad, you could use a more specific type like Mongoose
from the mongoose package. This would provide better type safety and autocompletion.
-let MONGOOSE_INSTANCE: typeof mongoose;
+import type { Mongoose } from 'mongoose';
+let MONGOOSE_INSTANCE: Mongoose;
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
let MONGOOSE_INSTANCE: typeof mongoose; | |
import type { Mongoose } from 'mongoose'; | |
let MONGOOSE_INSTANCE: Mongoose; |
/** | ||
* ChatMessage Schema | ||
* | ||
* This schema defines the structure of a chat message document in the database. | ||
* | ||
* Fields: | ||
* - chatMessageBelongsTo: ObjectId, ref: "Chat", required | ||
* - The chat to which this message belongs. | ||
* - sender: ObjectId, ref: "User", required | ||
* - The user who sent the message. | ||
* - replyTo: ObjectId, ref: "ChatMessage", optional | ||
* - The message to which this message is a reply. | ||
* - messageContent: String, required | ||
* - The content of the message. | ||
* - type: String, required, enum: ["STRING", "VIDEO", "IMAGE", "FILE"] | ||
* - The type of the message content. | ||
* - status: String, required, enum: ["ACTIVE", "BLOCKED", "DELETED"], default: "ACTIVE" | ||
* - The status of the message. | ||
* - deletedBy: Array of ObjectId, ref: "User", optional | ||
* - List of users who have deleted the message. | ||
* - updatedAt: Date, required | ||
* - The date when the message was last updated. | ||
* - createdAt: Date, required | ||
* - The date when the message was created. | ||
* | ||
* Options: | ||
* - timestamps: Automatically adds createdAt and updatedAt fields. | ||
*/ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Update JSDoc comments to match the current schema.
The JSDoc comments include a description for a type
field that doesn't exist in the actual schema. Additionally, some descriptions (e.g., for messageContent
) still refer to "direct chat message". Please update the documentation to accurately reflect the current schema structure and field descriptions.
const userExists = (await User.exists({ | ||
_id: { $in: args.data.userIds }, | ||
})) as unknown as string[]; | ||
|
||
if (userExists && userExists.length !== args.data.userIds.length) { | ||
// Find which user ID(s) do not exist | ||
const missingUsers = args.data.userIds.filter( | ||
(id) => !userExists.includes(id), | ||
); | ||
throw new errors.NotFoundError( | ||
requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE), | ||
USER_NOT_FOUND_ERROR.CODE, | ||
JSON.stringify({ missingUsers: missingUsers }), | ||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Correct the usage of User.exists()
and fix the logic for user existence check
The User.exists()
method returns a boolean value or null
, not an array of user IDs. Casting it to string[]
and using .length
or .includes()
is incorrect. Instead, use User.find()
to retrieve existing users and compare their IDs with args.data.userIds
to identify any missing users.
Apply this diff to fix the logic:
- const userExists = (await User.exists({
- _id: { $in: args.data.userIds },
- })) as unknown as string[];
- if (userExists && userExists.length !== args.data.userIds.length) {
- // Find which user ID(s) do not exist
- const missingUsers = args.data.userIds.filter(
- (id) => !userExists.includes(id),
- );
- throw new errors.NotFoundError(
- requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE),
- USER_NOT_FOUND_ERROR.CODE,
- JSON.stringify({ missingUsers: missingUsers }),
- );
- }
+ const existingUsers = await User.find({
+ _id: { $in: args.data.userIds },
+ }).select('_id');
+ const existingUserIds = existingUsers.map((user) => user._id.toString());
+ if (existingUserIds.length !== args.data.userIds.length) {
+ // Find which user ID(s) do not exist
+ const missingUsers = args.data.userIds.filter(
+ (id) => !existingUserIds.includes(id),
+ );
+ throw new errors.NotFoundError(
+ requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE),
+ USER_NOT_FOUND_ERROR.CODE,
+ JSON.stringify({ missingUsers }),
+ );
+ }
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
const userExists = (await User.exists({ | |
_id: { $in: args.data.userIds }, | |
})) as unknown as string[]; | |
if (userExists && userExists.length !== args.data.userIds.length) { | |
// Find which user ID(s) do not exist | |
const missingUsers = args.data.userIds.filter( | |
(id) => !userExists.includes(id), | |
); | |
throw new errors.NotFoundError( | |
requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE), | |
USER_NOT_FOUND_ERROR.CODE, | |
JSON.stringify({ missingUsers: missingUsers }), | |
); | |
const existingUsers = await User.find({ | |
_id: { $in: args.data.userIds }, | |
}).select('_id'); | |
const existingUserIds = existingUsers.map((user) => user._id.toString()); | |
if (existingUserIds.length !== args.data.userIds.length) { | |
// Find which user ID(s) do not exist | |
const missingUsers = args.data.userIds.filter( | |
(id) => !existingUserIds.includes(id), | |
); | |
throw new errors.NotFoundError( | |
requestContext.translate(USER_NOT_FOUND_ERROR.MESSAGE), | |
USER_NOT_FOUND_ERROR.CODE, | |
JSON.stringify({ missingUsers }), | |
); | |
} |
if (args.data.isGroup && args.data.organizationId) { | ||
const organizationFoundInCache = await findOrganizationsInCache([ | ||
args.data.organizationId, | ||
]); | ||
|
||
organization = organizationFoundInCache[0]; | ||
|
||
// Checks whether organization with _id === args.data.organizationId exists. | ||
if (!organization) { | ||
throw new errors.NotFoundError( | ||
requestContext.translate(ORGANIZATION_NOT_FOUND_ERROR.MESSAGE), | ||
ORGANIZATION_NOT_FOUND_ERROR.CODE, | ||
ORGANIZATION_NOT_FOUND_ERROR.PARAM, | ||
); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ensure organizationId
is provided when creating a group chat
When args.data.isGroup
is true
, but organizationId
is not provided, the code does not handle this scenario and proceeds without checking the organization's existence. This could lead to creating a group chat without an associated organization, which may not be intended. Consider adding a validation to ensure that organizationId
is provided when isGroup
is true
, and throw a validation error if it's missing.
Apply this diff to enforce the validation:
if (args.data.isGroup && args.data.organizationId) {
+ // Ensure organizationId is provided for group chats
+} else if (args.data.isGroup && !args.data.organizationId) {
+ throw new errors.ValidationError(
+ 'Organization ID must be provided for group chats.',
+ 'ORGANIZATION_ID_REQUIRED',
+ );
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
if (args.data.isGroup && args.data.organizationId) { | |
const organizationFoundInCache = await findOrganizationsInCache([ | |
args.data.organizationId, | |
]); | |
organization = organizationFoundInCache[0]; | |
// Checks whether organization with _id === args.data.organizationId exists. | |
if (!organization) { | |
throw new errors.NotFoundError( | |
requestContext.translate(ORGANIZATION_NOT_FOUND_ERROR.MESSAGE), | |
ORGANIZATION_NOT_FOUND_ERROR.CODE, | |
ORGANIZATION_NOT_FOUND_ERROR.PARAM, | |
); | |
} | |
} | |
if (args.data.isGroup && args.data.organizationId) { | |
const organizationFoundInCache = await findOrganizationsInCache([ | |
args.data.organizationId, | |
]); | |
organization = organizationFoundInCache[0]; | |
// Checks whether organization with _id === args.data.organizationId exists. | |
if (!organization) { | |
throw new errors.NotFoundError( | |
requestContext.translate(ORGANIZATION_NOT_FOUND_ERROR.MESSAGE), | |
ORGANIZATION_NOT_FOUND_ERROR.CODE, | |
ORGANIZATION_NOT_FOUND_ERROR.PARAM, | |
); | |
} | |
// Ensure organizationId is provided for group chats | |
} else if (args.data.isGroup && !args.data.organizationId) { | |
throw new errors.ValidationError( | |
'Organization ID must be provided for group chats.', | |
'ORGANIZATION_ID_REQUIRED', | |
); | |
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Outside diff range and nitpick comments (5)
setup.ts (5)
Line range hint
125-176
: Refactor repeated environment file handling to improve maintainabilityIn the
accessAndRefreshTokens
function, the code handling environment variables for.env
and.env_test
is duplicated. This pattern repeats in multiple functions. Consider refactoring this logic into a helper function to avoid code duplication and enhance maintainability.
Line range hint
194-213
: Ensure MongoDB client is closed properly by adding afinally
blockIn the
wipeExistingData
function, the MongoDB client is closed after thetry
/catch
blocks. However, if an error occurs before theclient.close()
call, the client may remain open. To ensure the client is always closed, move theclient.close()
call into afinally
block.Apply this diff to fix the issue:
export async function wipeExistingData(url: string): Promise<void> { const client = new MongoClient(`${url}`); try { await client.connect(); const db = client.db(); const collections = await db.listCollections().toArray(); if (collections.length > 0) { for (const collection of collections) { await db.collection(collection.name).deleteMany({}); } console.log("All existing data has been deleted."); } } catch { console.error("Could not connect to database to check for data"); + } finally { + client.close(); } }
Line range hint
218-234
: Ensure MongoDB client is closed properly by adding afinally
blockIn the
checkDb
function, the MongoDB client should be closed in afinally
block to ensure it is always closed, even if an error occurs during the database operation.Apply this diff to fix the issue:
export async function checkDb(url: string): Promise<boolean> { let dbEmpty = false; const client = new MongoClient(`${url}`); try { await client.connect(); const db = client.db(); const collections = await db.listCollections().toArray(); if (collections.length > 0) { console.log("Existing data found in the database"); dbEmpty = false; } else { dbEmpty = true; } } catch { console.error("Could not connect to database to check for data"); + } finally { + client.close(); } return dbEmpty; }
Line range hint
717-726
: Add validation before deleting the MinIO data directory to prevent accidental data lossWhen removing the existing MinIO data directory, the code does not validate
currentDataDir
. This could lead to accidental deletion of unintended directories ifcurrentDataDir
is incorrectly set. Please add validation to ensure thatcurrentDataDir
is within the expected directory.Apply this diff to add validation:
if (changeDataDir && currentDataDir) { + // Validate that currentDataDir is within the project directory + const absoluteDataDir = path.resolve(currentDataDir); + const projectRoot = process.cwd(); + if (!absoluteDataDir.startsWith(projectRoot)) { + console.error(`[MINIO] Invalid data directory path: ${currentDataDir}`); + return; + } try { fs.rmSync(currentDataDir, { recursive: true, force: true }); console.log( `[MINIO] Removed existing data directory: ${currentDataDir}`, ); } catch (err) { console.error(`[MINIO] Error removing existing data directory: ${err}`); } }
Line range hint
125-176
: Consolidate environment configuration logic to eliminate duplicationThe environment variable handling for
.env
and.env_test
is duplicated across multiple functions (accessAndRefreshTokens
,setNodeEnvironment
,redisConfiguration
,superAdmin
,twoFactorAuth
). Consider creating a utility function to read and update the correct environment file based onprocess.env.NODE_ENV
to improve maintainability.Also applies to: 364-393, 439-466, 494-521
What kind of change does this PR introduce?
Issue Number:
Fixes #2465
Did you add tests for your changes?
Snapshots/Videos:
If relevant, did you update the documentation?
Summary
Does this PR introduce a breaking change?
Other information
Have you read the contributing guide?
Summary by CodeRabbit
New Features
Chat
andChatMessage
structure for improved chat functionality.createChat
mutation to facilitate the creation of both direct and group chats.sendMessageToChat
mutation for sending messages to any chat type.chatById
andchatsByUserId
for retrieving chat information.messageSentToChat
.Bug Fixes
Documentation
Tests
Chores