import { Injectable } from '@angular/core'; import { IMessage, MessageAttachmentSource, MessageEntity, MessageEntitySchema, } from '../../../../../core/chat/entity/message'; import { AttachmentLocalDataSource } from "src/app/module/chat/data/repository/attachment/attachment-local-repository.service"; import { z } from 'zod'; import { v4 as uuidv4 } from 'uuid'; import { InstanceId } from '../../chat-service.service'; import { createBlobFromBase64, createDataURL } from 'src/app/utils/ToBase64'; import { zodSafeValidation } from 'src/app/utils/zodValidation'; import { Logger } from 'src/app/services/logger/main/service'; import { err, Result } from 'neverthrow'; import { MessageMapper } from '../../mapper/messageMapper'; import { RoomType } from "src/app/core/chat/entity/group"; import { TracingType, XTracerAsync } from 'src/app/services/monitoring/opentelemetry/tracer'; import { MemberListLocalRepository } from 'src/app/module/chat/data/repository/member/member-list-local-repository.service' import { SessionStore } from 'src/app/store/session.service'; import { MessageTable } from 'src/app/infra/database/dexie/instance/chat/schema/message'; import { MessageAttachmentFileType, MessageOutPutDataDTO } from 'src/app/core/chat/repository/dto/messageOutputDTO'; import { IMessageLocalRepository } from 'src/app/core/chat/repository/message/message-local-repository'; import { IMessageSocketRepository } from 'src/app/core/chat/repository/message/message-socket-repository'; import { IMemberLocalRepository } from 'src/app/core/chat/repository/member/member-local-repository'; import { IAttachmentLocalRepository } from 'src/app/core/chat/repository/typing/typing-local-repository'; import { base64Schema } from 'src/app/utils/zod'; export const MessageInputDTOSchema = z.object({ roomId: z.string().uuid().optional(), receiverId: z.number().optional(), senderId: z.number(), message: z.string().nullable().optional(), messageType: z.number(), canEdit: z.boolean(), oneShot: z.boolean(), requireUnlock: z.boolean(), requestId: z.string(), attachment: z.object({ fileType: z.nativeEnum(MessageAttachmentFileType), source: z.nativeEnum(MessageAttachmentSource), file: base64Schema.optional(), fileName: z.string().optional(), applicationId: z.number().optional(), docId: z.number().optional(), mimeType: z.string().optional(), description: z.string().optional() }).optional() }); export type MessageInputDTO = z.infer export const MessageCreatePutDataDTOSchema = z.object({ id: z.string(), roomId: z.string(), sender: z.object({ wxUserId: z.number(), wxFullName: z.string(), wxeMail: z.string(), userPhoto: z.string().optional() }), message: z.string().nullable().optional(), messageType: z.number(), sentAt: z.string(), canEdit: z.boolean(), oneShot: z.boolean(), requireUnlock: z.boolean(), requestId: z.string().optional().nullable(), reactions: z.object({ id: z.string(), reactedAt: z.string(), reaction: z.string(), sender: z.object({}), }).array(), info: z.array(z.object({ memberId: z.number(), readAt: z.string().nullable(), deliverAt: z.string().nullable() })), attachments: z.array(z.object({ fileType: z.nativeEnum(MessageAttachmentFileType), source: z.nativeEnum(MessageAttachmentSource), file: z.string().optional(), fileName: z.string().optional(), applicationId: z.number().optional(), docId: z.string().optional(), id: z.string().optional() })) }); export type MessageCreateOutPutDataDTO = z.infer @Injectable({ providedIn: 'root' }) export class MessageCreateUseCaseService { constructor( private AttachmentLocalRepositoryService: IAttachmentLocalRepository, private messageLocalDataSourceService: IMessageLocalRepository, private messageSocketRepositoryService: IMessageSocketRepository, private MemberListLocalRepository: IMemberLocalRepository ) { } @XTracerAsync({name:'MessageCreateUseCaseService', module:'chat', bugPrint: true, waitNThrow: 5000}) async execute(message: IMessage, messageEnum: RoomType, tracing?: TracingType) { const validation = zodSafeValidation(MessageEntitySchema, message) if(validation.isOk()) { message.sendAttemp++; message.requestId = InstanceId +'@'+ uuidv4(); message.sending = true; const createMessageLocally = await this.messageLocalDataSourceService.insert(message) if(createMessageLocally.isOk()) { message.$id = createMessageLocally.value if(message.hasAttachment) { for (const attachment of message.attachments) { if(attachment.source != MessageAttachmentSource.Webtrix) { this.AttachmentLocalRepositoryService.insert({ $messageId: createMessageLocally.value, base64: createDataURL(attachment.file, attachment.mimeType), fileType: attachment.fileType, source: attachment.source, fileName: attachment.fileName, applicationId: attachment.applicationId, docId: attachment.docId, mimeType: attachment.mimeType, }).then((e) => { if(e.isErr()) { Logger.error('failed to create attachment locally on send message', { error: e.error, data: createDataURL(attachment.file, attachment.mimeType).slice(0, 100) +'...' }) } }) attachment.safeFile = createDataURL(attachment.file, attachment.mimeType) } } } //==================== message.sending = true let sendMessageResult: Result if(messageEnum == RoomType.Group) { const DTO = MessageMapper.fromDomain(message, message.requestId) sendMessageResult = await this.messageSocketRepositoryService.sendGroupMessage(DTO) } else { if(message.receiverId) { const DTO = MessageMapper.fromDomain(message, message.requestId) sendMessageResult = await this.messageSocketRepositoryService.sendDirectMessage(DTO) } else { const getRoomMembers = await this.MemberListLocalRepository.directMember({ roomId:message.roomId, currentUserId: SessionStore.user.UserId }) if(getRoomMembers.isOk()) { message.receiverId = getRoomMembers.value.wxUserId const DTO = MessageMapper.fromDomain(message, message.requestId) sendMessageResult = await this.messageSocketRepositoryService.sendGroupMessage(DTO) } else { console.log('not found direct users', getRoomMembers.error) } } } // return this sendMessageResult if(sendMessageResult.isOk()) { console.log('sendMessageResult', sendMessageResult.value.id) if(sendMessageResult.value.sender == undefined || sendMessageResult.value.sender == null) { delete sendMessageResult.value.sender } let clone: MessageTable = { ...sendMessageResult.value, id: sendMessageResult.value.id, $id : message.$id } this.messageLocalDataSourceService.update(message.$id, {...clone, sending: false, roomId: message.roomId}).then((data)=> { if(data.isOk()) { } else { tracing.hasError('failed to update send message') console.log(sendMessageResult) console.log(data.error) } }) return sendMessageResult } else { Logger.error('failed to send message to the server', { error: sendMessageResult.error }) await this.messageLocalDataSourceService.update(message.$id, {sending: false, $id: message.$id}) return err('no connection') } } else { Logger.error('failed to insert locally', { error: createMessageLocally.error.message }) } } else { if(validation.error.formErrors.fieldErrors.attachments) { Logger.error('failed to send message doe to invalid attachment', { zodErrorList: validation.error.errors, data: message.attachments }) } else { Logger.error('failed to send message, validation failed', { zodErrorList: validation.error.errors, data: message }) } } } }