chore: update subproject commit and enhance GraphQL message schema
- Updated the subproject commit reference in epess-database. - Added logging functionality in the GraphQL module for subscription handling to improve debugging. - Refactored the MessageSchema to standardize formatting, enhance readability, and add a new field `isRead` to track message status. - Improved error handling and validation in message-related operations, ensuring better user experience and data integrity. - Updated the generated types in pothos.generated.ts to reflect the latest schema changes. These changes aim to improve code maintainability, enhance debugging capabilities, and ensure a more robust message handling process.
This commit is contained in:
Submodule epess-database updated: d6f38be8e8...e5ffda1a0d
@@ -112,6 +112,9 @@ import { GraphqlService } from './graphql.service'
|
|||||||
installSubscriptionHandlers: true,
|
installSubscriptionHandlers: true,
|
||||||
subscriptions: {
|
subscriptions: {
|
||||||
'graphql-ws': {
|
'graphql-ws': {
|
||||||
|
onSubscribe(ctx, message) {
|
||||||
|
console.log('onSubscribe', ctx, message)
|
||||||
|
},
|
||||||
onConnect: (ctx: Context<Record<string, unknown>>) => {
|
onConnect: (ctx: Context<Record<string, unknown>>) => {
|
||||||
if (!ctx.connectionParams) {
|
if (!ctx.connectionParams) {
|
||||||
Logger.log('No connectionParams provided', 'GraphqlModule')
|
Logger.log('No connectionParams provided', 'GraphqlModule')
|
||||||
|
|||||||
@@ -1,62 +1,75 @@
|
|||||||
import { Inject, Injectable, Logger } from '@nestjs/common'
|
import { Inject, Injectable, Logger } from "@nestjs/common";
|
||||||
import { ChatRoomType, Message, MessageContextType, MessageType } from '@prisma/client'
|
import {
|
||||||
import { Pothos, PothosRef, PothosSchema, SchemaBuilderToken } from '@smatch-corp/nestjs-pothos'
|
ChatRoomType,
|
||||||
import { Builder, SchemaContext } from '../Graphql/graphql.builder'
|
Message,
|
||||||
import { PrismaService } from '../Prisma/prisma.service'
|
MessageContextType,
|
||||||
import { PubSubEvent } from '../common/pubsub/pubsub-event'
|
MessageType,
|
||||||
import { DateTimeUtils } from '../common/utils/datetime.utils'
|
} from "@prisma/client";
|
||||||
|
import {
|
||||||
|
Pothos,
|
||||||
|
PothosRef,
|
||||||
|
PothosSchema,
|
||||||
|
SchemaBuilderToken,
|
||||||
|
} from "@smatch-corp/nestjs-pothos";
|
||||||
|
import { Builder, SchemaContext } from "../Graphql/graphql.builder";
|
||||||
|
import { PrismaService } from "../Prisma/prisma.service";
|
||||||
|
import { PubSubEvent } from "../common/pubsub/pubsub-event";
|
||||||
|
import { DateTimeUtils } from "../common/utils/datetime.utils";
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MessageSchema extends PothosSchema {
|
export class MessageSchema extends PothosSchema {
|
||||||
constructor(
|
constructor(
|
||||||
@Inject(SchemaBuilderToken) private readonly builder: Builder,
|
@Inject(SchemaBuilderToken) private readonly builder: Builder,
|
||||||
private readonly prisma: PrismaService,
|
private readonly prisma: PrismaService
|
||||||
) {
|
) {
|
||||||
super()
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
@PothosRef()
|
@PothosRef()
|
||||||
message() {
|
message() {
|
||||||
return this.builder.prismaObject('Message', {
|
return this.builder.prismaObject("Message", {
|
||||||
description: 'A message in the system.',
|
description: "A message in the system.",
|
||||||
fields: (t) => ({
|
fields: (t) => ({
|
||||||
id: t.exposeID('id', {
|
id: t.exposeID("id", {
|
||||||
description: 'The ID of the message.',
|
description: "The ID of the message.",
|
||||||
}),
|
}),
|
||||||
senderId: t.exposeID('senderId', {
|
senderId: t.exposeID("senderId", {
|
||||||
description: 'The ID of the sender.',
|
description: "The ID of the sender.",
|
||||||
}),
|
}),
|
||||||
chatRoomId: t.exposeID('chatRoomId', {
|
chatRoomId: t.exposeID("chatRoomId", {
|
||||||
description: 'The ID of the chat room.',
|
description: "The ID of the chat room.",
|
||||||
}),
|
}),
|
||||||
type: t.expose('type', {
|
type: t.expose("type", {
|
||||||
type: MessageType,
|
type: MessageType,
|
||||||
description: 'The type of the message.',
|
description: "The type of the message.",
|
||||||
}),
|
}),
|
||||||
content: t.exposeString('content', {
|
content: t.exposeString("content", {
|
||||||
description: 'The message content.',
|
description: "The message content.",
|
||||||
}),
|
}),
|
||||||
sentAt: t.expose('sentAt', {
|
sentAt: t.expose("sentAt", {
|
||||||
type: 'DateTime',
|
type: "DateTime",
|
||||||
description: 'The date and time the message was sent.',
|
description: "The date and time the message was sent.",
|
||||||
}),
|
}),
|
||||||
context: t.expose('context', {
|
context: t.expose("context", {
|
||||||
type: MessageContextType,
|
type: MessageContextType,
|
||||||
description: 'The context of the message.',
|
description: "The context of the message.",
|
||||||
}),
|
}),
|
||||||
metadata: t.expose('metadata', {
|
metadata: t.expose("metadata", {
|
||||||
type: 'Json',
|
type: "Json",
|
||||||
nullable: true,
|
nullable: true,
|
||||||
description: 'The metadata of the message.',
|
description: "The metadata of the message.",
|
||||||
}),
|
}),
|
||||||
sender: t.relation('sender', {
|
sender: t.relation("sender", {
|
||||||
description: 'The sender of the message.',
|
description: "The sender of the message.",
|
||||||
}),
|
}),
|
||||||
chatRoom: t.relation('chatRoom', {
|
chatRoom: t.relation("chatRoom", {
|
||||||
description: 'The chat room.',
|
description: "The chat room.",
|
||||||
|
}),
|
||||||
|
isRead: t.exposeBoolean("isRead", {
|
||||||
|
description: "Whether the message has been read.",
|
||||||
}),
|
}),
|
||||||
}),
|
}),
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@Pothos()
|
@Pothos()
|
||||||
@@ -64,30 +77,35 @@ export class MessageSchema extends PothosSchema {
|
|||||||
this.builder.queryFields((t) => ({
|
this.builder.queryFields((t) => ({
|
||||||
message: t.prismaField({
|
message: t.prismaField({
|
||||||
type: this.message(),
|
type: this.message(),
|
||||||
description: 'Retrieve a single message by its unique identifier.',
|
description: "Retrieve a single message by its unique identifier.",
|
||||||
args: this.builder.generator.findUniqueArgs('Message'),
|
args: this.builder.generator.findUniqueArgs("Message"),
|
||||||
resolve: async (query, _root, args) => {
|
resolve: async (query, _root, args) => {
|
||||||
return await this.prisma.message.findUnique({
|
return await this.prisma.message.findUnique({
|
||||||
...query,
|
...query,
|
||||||
where: args.where,
|
where: args.where,
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
messages: t.prismaField({
|
messages: t.prismaField({
|
||||||
type: [this.message()],
|
type: [this.message()],
|
||||||
description: 'Retrieve a list of messages with optional filtering, ordering, and pagination.',
|
description:
|
||||||
args: this.builder.generator.findManyArgs('Message'),
|
"Retrieve a list of messages with optional filtering, ordering, and pagination.",
|
||||||
|
args: this.builder.generator.findManyArgs("Message"),
|
||||||
resolve: async (query, _root, args, ctx, _info) => {
|
resolve: async (query, _root, args, ctx, _info) => {
|
||||||
if (ctx.isSubscription) {
|
if (ctx.isSubscription) {
|
||||||
throw new Error('Not allowed')
|
throw new Error("Not allowed");
|
||||||
}
|
}
|
||||||
if (args.filter?.context && typeof args.filter.context === 'object') {
|
if (args.filter?.context && typeof args.filter.context === "object") {
|
||||||
// if args.context is NOTIFICATION or SYSTEM, filter by recipientId
|
// if args.context is NOTIFICATION or SYSTEM, filter by recipientId
|
||||||
if (
|
if (
|
||||||
args.filter.context.in?.toString().includes(MessageContextType.NOTIFICATION) ||
|
args.filter.context.in
|
||||||
args.filter.context.in?.toString().includes(MessageContextType.SYSTEM)
|
?.toString()
|
||||||
|
.includes(MessageContextType.NOTIFICATION) ||
|
||||||
|
args.filter.context.in
|
||||||
|
?.toString()
|
||||||
|
.includes(MessageContextType.SYSTEM)
|
||||||
) {
|
) {
|
||||||
args.filter.recipientId = ctx.http.me?.id
|
args.filter.recipientId = ctx.http.me?.id;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return await this.prisma.message.findMany({
|
return await this.prisma.message.findMany({
|
||||||
@@ -96,63 +114,63 @@ export class MessageSchema extends PothosSchema {
|
|||||||
take: args.take ?? undefined,
|
take: args.take ?? undefined,
|
||||||
orderBy: args.orderBy ?? undefined,
|
orderBy: args.orderBy ?? undefined,
|
||||||
where: args.filter ?? undefined,
|
where: args.filter ?? undefined,
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
messagesByChatRoomId: t.prismaField({
|
messagesByChatRoomId: t.prismaField({
|
||||||
type: [this.message()],
|
type: [this.message()],
|
||||||
description: 'Retrieve a list of messages by chat room ID.',
|
description: "Retrieve a list of messages by chat room ID.",
|
||||||
args: this.builder.generator.findManyArgs('Message'),
|
args: this.builder.generator.findManyArgs("Message"),
|
||||||
resolve: async (query, _root, args) => {
|
resolve: async (query, _root, args) => {
|
||||||
return await this.prisma.message.findMany({
|
return await this.prisma.message.findMany({
|
||||||
...query,
|
...query,
|
||||||
where: args.filter ?? undefined,
|
where: args.filter ?? undefined,
|
||||||
})
|
});
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
}))
|
}));
|
||||||
|
|
||||||
// mutations
|
// mutations
|
||||||
this.builder.mutationFields((t) => ({
|
this.builder.mutationFields((t) => ({
|
||||||
sendMessage: t.prismaField({
|
sendMessage: t.prismaField({
|
||||||
type: this.message(),
|
type: this.message(),
|
||||||
description: 'Send a message to a chat room.',
|
description: "Send a message to a chat room.",
|
||||||
args: {
|
args: {
|
||||||
input: t.arg({
|
input: t.arg({
|
||||||
type: this.builder.generator.getCreateInput('Message', [
|
type: this.builder.generator.getCreateInput("Message", [
|
||||||
'id',
|
"id",
|
||||||
'senderId',
|
"senderId",
|
||||||
'sender',
|
"sender",
|
||||||
'sentAt',
|
"sentAt",
|
||||||
'context',
|
"context",
|
||||||
'recipient',
|
"recipient",
|
||||||
'recipientId',
|
"recipientId",
|
||||||
]),
|
]),
|
||||||
description: 'The message to send.',
|
description: "The message to send.",
|
||||||
required: true,
|
required: true,
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
resolve: async (query, _root, args, ctx, _info) => {
|
resolve: async (query, _root, args, ctx, _info) => {
|
||||||
if (ctx.isSubscription) {
|
if (ctx.isSubscription) {
|
||||||
throw new Error('Not allowed')
|
throw new Error("Not allowed");
|
||||||
}
|
}
|
||||||
const messageContext = MessageContextType.CHAT
|
const messageContext = MessageContextType.CHAT;
|
||||||
// get the sender from the context and add it to the input
|
// get the sender from the context and add it to the input
|
||||||
args.input.sender = {
|
args.input.sender = {
|
||||||
connect: {
|
connect: {
|
||||||
id: ctx.http.me?.id,
|
id: ctx.http.me?.id,
|
||||||
},
|
},
|
||||||
}
|
};
|
||||||
if (!args.input.sender) {
|
if (!args.input.sender) {
|
||||||
throw new Error('Cannot get sender from context')
|
throw new Error("Cannot get sender from context");
|
||||||
}
|
}
|
||||||
let userIds: string[] = []
|
let userIds: string[] = [];
|
||||||
// get the recipient if messageContext is CHAT
|
// get the recipient if messageContext is CHAT
|
||||||
if (messageContext === MessageContextType.CHAT) {
|
if (messageContext === MessageContextType.CHAT) {
|
||||||
// get chatRoomId from input
|
// get chatRoomId from input
|
||||||
const chatRoomId = args.input.chatRoom?.connect?.id
|
const chatRoomId = args.input.chatRoom?.connect?.id;
|
||||||
if (!chatRoomId) {
|
if (!chatRoomId) {
|
||||||
throw new Error('Cannot get chatRoomId from input')
|
throw new Error("Cannot get chatRoomId from input");
|
||||||
}
|
}
|
||||||
// if chatroom type is SUPPORT, user 1 is mentorId, user 2 is customerId
|
// if chatroom type is SUPPORT, user 1 is mentorId, user 2 is customerId
|
||||||
// query the chatRoom to get the userIds
|
// query the chatRoom to get the userIds
|
||||||
@@ -160,16 +178,16 @@ export class MessageSchema extends PothosSchema {
|
|||||||
where: {
|
where: {
|
||||||
id: chatRoomId,
|
id: chatRoomId,
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
if (chatRoom?.type === ChatRoomType.SUPPORT) {
|
if (chatRoom?.type === ChatRoomType.SUPPORT) {
|
||||||
userIds = [chatRoom.mentorId!, chatRoom.customerId!]
|
userIds = [chatRoom.mentorId!, chatRoom.customerId!];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// check if content is empty
|
// check if content is empty
|
||||||
if (!args.input.content || args.input.content.trim() === '') {
|
if (!args.input.content || args.input.content.trim() === "") {
|
||||||
throw new Error('Content cannot be empty')
|
throw new Error("Content cannot be empty");
|
||||||
}
|
}
|
||||||
const lastActivity = DateTimeUtils.now()
|
const lastActivity = DateTimeUtils.now();
|
||||||
const message = await this.prisma.$transaction(async (tx) => {
|
const message = await this.prisma.$transaction(async (tx) => {
|
||||||
const message = await tx.message.create({
|
const message = await tx.message.create({
|
||||||
...query,
|
...query,
|
||||||
@@ -177,7 +195,7 @@ export class MessageSchema extends PothosSchema {
|
|||||||
...args.input,
|
...args.input,
|
||||||
context: MessageContextType.CHAT,
|
context: MessageContextType.CHAT,
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
await tx.chatRoom.update({
|
await tx.chatRoom.update({
|
||||||
where: {
|
where: {
|
||||||
id: message.chatRoomId!,
|
id: message.chatRoomId!,
|
||||||
@@ -185,39 +203,45 @@ export class MessageSchema extends PothosSchema {
|
|||||||
data: {
|
data: {
|
||||||
lastActivity: lastActivity.toJSDate(),
|
lastActivity: lastActivity.toJSDate(),
|
||||||
},
|
},
|
||||||
})
|
});
|
||||||
return message
|
return message;
|
||||||
})
|
});
|
||||||
ctx.http.pubSub.publish(`${PubSubEvent.MESSAGE_SENT}.${message.chatRoomId}`, message)
|
ctx.http.pubSub.publish(
|
||||||
|
`${PubSubEvent.MESSAGE_SENT}.${message.chatRoomId}`,
|
||||||
|
message
|
||||||
|
);
|
||||||
// publish to new message subscribers
|
// publish to new message subscribers
|
||||||
userIds.forEach((userId: string) => {
|
userIds.forEach((userId: string) => {
|
||||||
ctx.http.pubSub.publish(`${PubSubEvent.NEW_MESSAGE}.${userId}`, message)
|
ctx.http.pubSub.publish(
|
||||||
})
|
`${PubSubEvent.NEW_MESSAGE}.${userId}`,
|
||||||
return message
|
message
|
||||||
|
);
|
||||||
|
});
|
||||||
|
return message;
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
}))
|
}));
|
||||||
|
|
||||||
this.builder.subscriptionFields((t) => ({
|
this.builder.subscriptionFields((t) => ({
|
||||||
messageSent: t.field({
|
messageSent: t.field({
|
||||||
description: 'Subscribe to messages sent by users.',
|
description: "Subscribe to messages sent by users.",
|
||||||
type: this.message(),
|
type: this.message(),
|
||||||
args: {
|
args: {
|
||||||
chatRoomId: t.arg({
|
chatRoomId: t.arg({
|
||||||
type: 'String',
|
type: "String",
|
||||||
description: 'The ID of the chat room to subscribe to.',
|
description: "The ID of the chat room to subscribe to.",
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
subscribe: (_, args, ctx: SchemaContext) => {
|
subscribe: (_, args, ctx: SchemaContext) => {
|
||||||
if (!ctx.isSubscription) {
|
if (!ctx.isSubscription) {
|
||||||
throw new Error('Not allowed')
|
throw new Error("Not allowed");
|
||||||
}
|
}
|
||||||
return ctx.websocket.pubSub.asyncIterator([
|
return ctx.websocket.pubSub.asyncIterator([
|
||||||
`${PubSubEvent.MESSAGE_SENT}.${args.chatRoomId}`,
|
`${PubSubEvent.MESSAGE_SENT}.${args.chatRoomId}`,
|
||||||
]) as unknown as AsyncIterable<Message>
|
]) as unknown as AsyncIterable<Message>;
|
||||||
},
|
},
|
||||||
resolve: (payload: Message) => payload,
|
resolve: (payload: Message) => payload,
|
||||||
}),
|
}),
|
||||||
}))
|
}));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user