- Refactored package.json to consolidate Jest configuration for improved readability. - Cleaned up launch.json by removing unnecessary whitespace for better formatting. - Updated CronService to refine service disabling logic based on recent activity, ensuring more accurate service management. - Enhanced MeetingRoom and WorkshopMeetingRoom schemas with consistent formatting and improved field descriptions for clarity. - Improved error handling in MeetingRoom schema to provide clearer feedback for unauthorized access and missing resources. - Updated pothos.generated.ts to reflect recent schema changes, ensuring type consistency across the application. These changes enhance the overall structure and maintainability of the codebase, improving service management and user experience.
224 lines
7.6 KiB
TypeScript
224 lines
7.6 KiB
TypeScript
import { Inject, Injectable, Logger } from '@nestjs/common'
|
|
import { ChatRoomType, Message, MessageContextType, MessageType } 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()
|
|
export class MessageSchema extends PothosSchema {
|
|
constructor(
|
|
@Inject(SchemaBuilderToken) private readonly builder: Builder,
|
|
private readonly prisma: PrismaService,
|
|
) {
|
|
super()
|
|
}
|
|
|
|
@PothosRef()
|
|
message() {
|
|
return this.builder.prismaObject('Message', {
|
|
description: 'A message in the system.',
|
|
fields: (t) => ({
|
|
id: t.exposeID('id', {
|
|
description: 'The ID of the message.',
|
|
}),
|
|
senderId: t.exposeID('senderId', {
|
|
description: 'The ID of the sender.',
|
|
}),
|
|
chatRoomId: t.exposeID('chatRoomId', {
|
|
description: 'The ID of the chat room.',
|
|
}),
|
|
type: t.expose('type', {
|
|
type: MessageType,
|
|
description: 'The type of the message.',
|
|
}),
|
|
content: t.exposeString('content', {
|
|
description: 'The message content.',
|
|
}),
|
|
sentAt: t.expose('sentAt', {
|
|
type: 'DateTime',
|
|
description: 'The date and time the message was sent.',
|
|
}),
|
|
context: t.expose('context', {
|
|
type: MessageContextType,
|
|
description: 'The context of the message.',
|
|
}),
|
|
metadata: t.expose('metadata', {
|
|
type: 'Json',
|
|
nullable: true,
|
|
description: 'The metadata of the message.',
|
|
}),
|
|
sender: t.relation('sender', {
|
|
description: 'The sender of the message.',
|
|
}),
|
|
chatRoom: t.relation('chatRoom', {
|
|
description: 'The chat room.',
|
|
}),
|
|
}),
|
|
})
|
|
}
|
|
|
|
@Pothos()
|
|
init(): void {
|
|
this.builder.queryFields((t) => ({
|
|
message: t.prismaField({
|
|
type: this.message(),
|
|
description: 'Retrieve a single message by its unique identifier.',
|
|
args: this.builder.generator.findUniqueArgs('Message'),
|
|
resolve: async (query, _root, args) => {
|
|
return await this.prisma.message.findUnique({
|
|
...query,
|
|
where: args.where,
|
|
})
|
|
},
|
|
}),
|
|
messages: t.prismaField({
|
|
type: [this.message()],
|
|
description: 'Retrieve a list of messages with optional filtering, ordering, and pagination.',
|
|
args: this.builder.generator.findManyArgs('Message'),
|
|
resolve: async (query, _root, args, ctx, _info) => {
|
|
if (ctx.isSubscription) {
|
|
throw new Error('Not allowed')
|
|
}
|
|
if (args.filter?.context && typeof args.filter.context === 'object') {
|
|
// if args.context is NOTIFICATION or SYSTEM, filter by recipientId
|
|
if (
|
|
args.filter.context.in?.toString().includes(MessageContextType.NOTIFICATION) ||
|
|
args.filter.context.in?.toString().includes(MessageContextType.SYSTEM)
|
|
) {
|
|
args.filter.recipientId = ctx.http.me?.id
|
|
}
|
|
}
|
|
return await this.prisma.message.findMany({
|
|
...query,
|
|
skip: args.skip ?? undefined,
|
|
take: args.take ?? undefined,
|
|
orderBy: args.orderBy ?? undefined,
|
|
where: args.filter ?? undefined,
|
|
})
|
|
},
|
|
}),
|
|
messagesByChatRoomId: t.prismaField({
|
|
type: [this.message()],
|
|
description: 'Retrieve a list of messages by chat room ID.',
|
|
args: this.builder.generator.findManyArgs('Message'),
|
|
resolve: async (query, _root, args) => {
|
|
return await this.prisma.message.findMany({
|
|
...query,
|
|
where: args.filter ?? undefined,
|
|
})
|
|
},
|
|
}),
|
|
}))
|
|
|
|
// mutations
|
|
this.builder.mutationFields((t) => ({
|
|
sendMessage: t.prismaField({
|
|
type: this.message(),
|
|
description: 'Send a message to a chat room.',
|
|
args: {
|
|
input: t.arg({
|
|
type: this.builder.generator.getCreateInput('Message', [
|
|
'id',
|
|
'senderId',
|
|
'sender',
|
|
'sentAt',
|
|
'context',
|
|
'recipient',
|
|
'recipientId',
|
|
]),
|
|
description: 'The message to send.',
|
|
required: true,
|
|
}),
|
|
},
|
|
resolve: async (query, _root, args, ctx, _info) => {
|
|
if (ctx.isSubscription) {
|
|
throw new Error('Not allowed')
|
|
}
|
|
const messageContext = MessageContextType.CHAT
|
|
// get the sender from the context and add it to the input
|
|
args.input.sender = {
|
|
connect: {
|
|
id: ctx.http.me?.id,
|
|
},
|
|
}
|
|
if (!args.input.sender) {
|
|
throw new Error('Cannot get sender from context')
|
|
}
|
|
let userIds: string[] = []
|
|
// get the recipient if messageContext is CHAT
|
|
if (messageContext === MessageContextType.CHAT) {
|
|
// get chatRoomId from input
|
|
const chatRoomId = args.input.chatRoom?.connect?.id
|
|
if (!chatRoomId) {
|
|
throw new Error('Cannot get chatRoomId from input')
|
|
}
|
|
// if chatroom type is SUPPORT, user 1 is mentorId, user 2 is customerId
|
|
// query the chatRoom to get the userIds
|
|
const chatRoom = await this.prisma.chatRoom.findUnique({
|
|
where: {
|
|
id: chatRoomId,
|
|
},
|
|
})
|
|
if (chatRoom?.type === ChatRoomType.SUPPORT) {
|
|
userIds = [chatRoom.mentorId!, chatRoom.customerId!]
|
|
}
|
|
}
|
|
// check if content is empty
|
|
if (!args.input.content || args.input.content.trim() === '') {
|
|
throw new Error('Content cannot be empty')
|
|
}
|
|
const lastActivity = DateTimeUtils.now()
|
|
const message = await this.prisma.$transaction(async (tx) => {
|
|
const message = await tx.message.create({
|
|
...query,
|
|
data: {
|
|
...args.input,
|
|
context: MessageContextType.CHAT,
|
|
},
|
|
})
|
|
await tx.chatRoom.update({
|
|
where: {
|
|
id: message.chatRoomId!,
|
|
},
|
|
data: {
|
|
lastActivity: lastActivity.toJSDate(),
|
|
},
|
|
})
|
|
return message
|
|
})
|
|
ctx.http.pubSub.publish(`${PubSubEvent.MESSAGE_SENT}.${message.chatRoomId}`, message)
|
|
// publish to new message subscribers
|
|
userIds.forEach((userId: string) => {
|
|
ctx.http.pubSub.publish(`${PubSubEvent.NEW_MESSAGE}.${userId}`, message)
|
|
})
|
|
return message
|
|
},
|
|
}),
|
|
}))
|
|
|
|
this.builder.subscriptionFields((t) => ({
|
|
messageSent: t.field({
|
|
description: 'Subscribe to messages sent by users.',
|
|
type: this.message(),
|
|
args: {
|
|
chatRoomId: t.arg({
|
|
type: 'String',
|
|
description: 'The ID of the chat room to subscribe to.',
|
|
}),
|
|
},
|
|
subscribe: (_, args, ctx: SchemaContext) => {
|
|
if (!ctx.isSubscription) {
|
|
throw new Error('Not allowed')
|
|
}
|
|
return ctx.websocket.pubSub.asyncIterator([
|
|
`${PubSubEvent.MESSAGE_SENT}.${args.chatRoomId}`,
|
|
]) as unknown as AsyncIterable<Message>
|
|
},
|
|
resolve: (payload: Message) => payload,
|
|
}),
|
|
}))
|
|
}
|
|
}
|