From b0e1dd74cd5a5c00e137bbb6796cc46679a1bb42 Mon Sep 17 00:00:00 2001 From: Peter Maquiran Date: Thu, 5 Sep 2024 11:45:54 +0100 Subject: [PATCH] fix message --- src/app/core/chat/entity/message.ts | 26 ++++----- .../dexie/instance/chat/schema/attachment.ts | 6 +-- .../dexie/instance/chat/schema/message.ts | 2 +- src/app/infra/repository/adapter.ts | 4 +- .../dexie/dexie-repository.service.ts | 26 +++++++-- .../message-local-data-source.service.ts | 21 ++++++-- ...e-download-attachment-user-case.service.ts | 51 ++++++++++++------ .../socket-message-update-use-case.service.ts | 2 +- .../component/messages/messages.page.html | 10 +++- .../chat/component/messages/messages.page.ts | 41 ++++++++------ .../modal/view-onces/view-onces.module.ts | 4 +- .../modal/view-onces/view-onces.page.html | 10 +++- .../chat/modal/view-onces/view-onces.page.ts | 15 +++--- src/app/ui/chat/store/model/message.ts | 42 ++++++++++++--- src/app/ui/chat/store/roomStore.ts | 53 +++++++++++++++++++ src/app/ui/chat/utils/messageBallon.ts | 2 +- 16 files changed, 237 insertions(+), 78 deletions(-) create mode 100644 src/app/ui/chat/store/roomStore.ts diff --git a/src/app/core/chat/entity/message.ts b/src/app/core/chat/entity/message.ts index a56d1d858..99aa9d009 100644 --- a/src/app/core/chat/entity/message.ts +++ b/src/app/core/chat/entity/message.ts @@ -19,6 +19,19 @@ export enum IMessageType { information } +export const MessageEntityAttachmentSchema = z.object({ + fileType: z.nativeEnum(MessageAttachmentFileType), + source: z.nativeEnum(MessageAttachmentSource), + file: base64Schema.optional(), + fileName: z.string().optional(), + applicationId: z.number().optional(), + docId: z.string().optional(), + id: z.string().optional(), + mimeType: z.string().optional(), + safeFile: z.any().optional(), + description: z.string().nullable().optional() +}) + export const MessageEntitySchema = z.object({ $id: z.any().optional(), id: z.string().uuid().optional(), @@ -50,18 +63,7 @@ export const MessageEntitySchema = z.object({ deliverAt: z.string().nullable() })).optional(), sending: z.boolean().optional(), - attachments: z.array(z.object({ - fileType: z.nativeEnum(MessageAttachmentFileType), - source: z.nativeEnum(MessageAttachmentSource), - file: base64Schema.optional(), - fileName: z.string().optional(), - applicationId: z.number().optional(), - docId: z.string().optional(), - id: z.string().optional(), - mimeType: z.string().optional(), - safeFile: z.any().optional(), - description: z.string().nullable().optional() - })).optional(), + attachments: z.array(MessageEntityAttachmentSchema).optional(), origin: z.enum(['history', 'local', 'incoming']).optional(), requestId: z.string().optional(), sendAttemp: z.number().optional(), diff --git a/src/app/infra/database/dexie/instance/chat/schema/attachment.ts b/src/app/infra/database/dexie/instance/chat/schema/attachment.ts index 3205e34c9..afe432daa 100644 --- a/src/app/infra/database/dexie/instance/chat/schema/attachment.ts +++ b/src/app/infra/database/dexie/instance/chat/schema/attachment.ts @@ -7,11 +7,11 @@ export const AttachmentTableSchema = z.object({ $id: z.number().optional(), // local id $messageId: z.string(), attachmentId: z.string().optional(), - file: z.instanceof(Blob), + file: z.instanceof(Blob).optional(), base64: zodDataUrlSchema.nullable().optional(), // - fileType: z.nativeEnum(MessageAttachmentFileType), - source: z.nativeEnum(MessageAttachmentSource), + fileType: z.nativeEnum(MessageAttachmentFileType).optional(), + source: z.nativeEnum(MessageAttachmentSource).optional(), fileName: z.string().optional(), applicationId: z.number().optional(), docId: z.string().optional(), diff --git a/src/app/infra/database/dexie/instance/chat/schema/message.ts b/src/app/infra/database/dexie/instance/chat/schema/message.ts index db85304b7..817cb5abb 100644 --- a/src/app/infra/database/dexie/instance/chat/schema/message.ts +++ b/src/app/infra/database/dexie/instance/chat/schema/message.ts @@ -49,5 +49,5 @@ export const MessageTableSchema = z.object({ export type MessageTable = z.infer export type DexieMessageTable = EntityTable; -export const messageTableColumn = '$id, id, roomId, senderId, message, messageType, canEdit, oneShot, requireUnlock' +export const messageTableColumn = '$id, id, roomId, senderId, message, messageType, canEdit, oneShot, requireUnlock, sending' diff --git a/src/app/infra/repository/adapter.ts b/src/app/infra/repository/adapter.ts index ca2c158cc..72bdbf4d8 100644 --- a/src/app/infra/repository/adapter.ts +++ b/src/app/infra/repository/adapter.ts @@ -1,11 +1,13 @@ import { Result } from 'neverthrow'; import { ZodError} from 'zod'; import { IDBError } from './types'; +import Dexie, { EntityTable, Table } from 'Dexie'; // Define a type for the Result of repository operations export type RepositoryResult = Result>; -export abstract class IDexieRepository { +export abstract class IDexieRepository> { + abstract createTransaction(callback: (table: I) => Promise): Promise abstract insert(document: T): Promise> abstract insertMany(documents: T[]): Promise> diff --git a/src/app/infra/repository/dexie/dexie-repository.service.ts b/src/app/infra/repository/dexie/dexie-repository.service.ts index 7abc9cd8c..eaef5b12d 100644 --- a/src/app/infra/repository/dexie/dexie-repository.service.ts +++ b/src/app/infra/repository/dexie/dexie-repository.service.ts @@ -1,5 +1,5 @@ import { Result, ok, err } from 'neverthrow'; -import { EntityTable } from 'Dexie'; +import Dexie, { EntityTable, Table } from 'Dexie'; import { ZodError, ZodObject, ZodSchema } from 'zod'; import { Logger } from 'src/app/services/logger/main/service'; import { IDexieRepository, RepositoryResult } from '../adapter' @@ -30,17 +30,37 @@ class DBError extends Error implements IDBError { } } -export class DexieRepository implements IDexieRepository { +export class DexieRepository> implements IDexieRepository { private table: EntityTable; private ZodSchema: ZodSchema private ZodPartialSchema: ZodSchema + private db: Dexie - constructor(table: EntityTable, ZodSchema: ZodSchema) { + constructor(table: EntityTable, ZodSchema: ZodSchema, db?:Dexie) { this.table = table as any this.ZodSchema = ZodSchema this.ZodPartialSchema = (ZodSchema as ZodObject).partial() as any; + this.db = db } + // Method to create a transaction and use the callback + async createTransaction(callback: (table:I) => Promise): Promise { + return this.db.transaction('rw', this.table, async () => { + try { + // Execute the callback function + await callback(this.table as any); + } catch (error) { + // Transactions are automatically rolled back on error + throw error; + } + }).then(() => { + console.log('Transaction completed successfully'); + }).catch((error) => { + console.error('Transaction failed: ' + error); + }); + } + + async insert(document: T): Promise> { const dataValidation = this.ZodSchema.safeParse(document) diff --git a/src/app/module/chat/data/repository/message/message-local-data-source.service.ts b/src/app/module/chat/data/repository/message/message-local-data-source.service.ts index bf12cd49c..e23ec12e7 100644 --- a/src/app/module/chat/data/repository/message/message-local-data-source.service.ts +++ b/src/app/module/chat/data/repository/message/message-local-data-source.service.ts @@ -3,7 +3,7 @@ import { liveQuery } from 'Dexie'; import { MessageEntity } from '../../../../../core/chat/entity/message'; import { DexieRepository } from 'src/app/infra/repository/dexie/dexie-repository.service'; import { Observable as DexieObservable, PromiseExtended } from 'Dexie'; -import { MessageTable, MessageTableSchema } from 'src/app/infra/database/dexie/instance/chat/schema/message'; +import { DexieMessageTable, MessageTable, MessageTableSchema } from 'src/app/infra/database/dexie/instance/chat/schema/message'; import { chatDatabase } from 'src/app/infra/database/dexie/service'; import { IMessageLocalRepository } from 'src/app/core/chat/repository/message/message-local-repository'; import { BehaviorSubject, combineLatest, from, Observable } from 'rxjs'; @@ -13,13 +13,13 @@ import { v4 as uuidv4 } from 'uuid' @Injectable({ providedIn: 'root' }) -export class MessageLocalDataSourceService extends DexieRepository implements IMessageLocalRepository { +export class MessageLocalDataSourceService extends DexieRepository implements IMessageLocalRepository { private creatingSubject : BehaviorSubject = new BehaviorSubject(null); private lastTimestamp = 0; constructor() { - super(chatDatabase.message, MessageTableSchema) + super(chatDatabase.message, MessageTableSchema, chatDatabase) this.setAllSenderToFalse(); this.onCreatingHook() @@ -54,22 +54,33 @@ export class MessageLocalDataSourceService extends DexieRepository { + // const result = await this.find({sending: true }) + // if(result.isOk()) { + // for(const message of result.value) { + // await this.update(message.$id, { sending: false }) + // } + // } + // }) + try { await chatDatabase.transaction('rw', chatDatabase.message, async () => { // Perform the update operation within the transaction await chatDatabase.message.toCollection().modify({ sending: false }); }); + // console.log('All messages updated successfully.'); } catch (error) { console.error('Error updating messages:', error); } } getItems(roomId: string): PromiseExtended { - return chatDatabase.message.where('roomId').equals(roomId).sortBy('$id') as any + return chatDatabase.message.where('roomId').equals(roomId).sortBy('$createAt') as any } getItemsLive(roomId: string): DexieObservable { - return liveQuery(() => chatDatabase.message.where('roomId').equals(roomId).sortBy('$id') as any) + return liveQuery(() => chatDatabase.message.where('roomId').equals(roomId).sortBy('$createAt') as any) } async getOfflineMessages () { diff --git a/src/app/module/chat/domain/use-case/message/message-download-attachment-user-case.service.ts b/src/app/module/chat/domain/use-case/message/message-download-attachment-user-case.service.ts index 80ab5baa7..44acee0f6 100644 --- a/src/app/module/chat/domain/use-case/message/message-download-attachment-user-case.service.ts +++ b/src/app/module/chat/domain/use-case/message/message-download-attachment-user-case.service.ts @@ -4,6 +4,8 @@ import { Logger } from 'src/app/services/logger/main/service'; import { convertBlobToDataURL, createBlobUrl } from 'src/app/utils/ToBase64'; import { AttachmentLocalDataSource } from 'src/app/module/chat/data/repository/attachment/attachment-local-repository.service' import { z } from 'zod'; +import { zodSafeValidation } from 'src/app/utils/zodValidation'; +import { IMessage, MessageEntitySchema } from 'src/app/core/chat/entity/message'; const DownloadMessageAttachmentByMessageIdSchema = z.object({ $messageId: z.string(), @@ -24,28 +26,43 @@ export class DownloadMessageAttachmentUserCaseService { ) { } async execute(input: DownloadMessageAttachmentByMessageId) { - const result = await this.AttachmentRemoteDataSourceService.getAttachment(input.$messageId) - return result.asyncMap(async (blob) => { - const dataUrl = await createBlobUrl(blob) + const validation = zodSafeValidation(DownloadMessageAttachmentByMessageIdSchema, input) - if(dataUrl.isOk()) { + if(validation.isOk()) { - Logger.info('downloaded file #1', { - // data: dataUrl.slice(0, 100)+'...', - context: 'DownloadMessageAttachmentUserCaseService' - }) + const result = await this.AttachmentRemoteDataSourceService.getAttachment(input.id) + return result.asyncMap(async (blob) => { - this.AttachmentLocalDataSource.insert({ - $messageId: input.$messageId, - id: input.id, - file: blob - }) + const dataUrl = await createBlobUrl(blob) - return dataUrl - } else { + if(dataUrl.isOk()) { + + Logger.info('downloaded file #1', { + // data: dataUrl.slice(0, 100)+'...', + context: 'DownloadMessageAttachmentUserCaseService' + }) + + this.AttachmentLocalDataSource.insert({ + $messageId: input.$messageId, + id: input.id, + file: blob + }) + + return dataUrl.value + } else { + + } + }) + } else { + + Logger.error('failed to download message doe to invalid attachment', { + zodErrorList: validation.error.errors, + data: input + }) + + return validation + } - } - }) } } diff --git a/src/app/module/chat/domain/use-case/socket/socket-message-update-use-case.service.ts b/src/app/module/chat/domain/use-case/socket/socket-message-update-use-case.service.ts index 2975bb71b..e681058b8 100644 --- a/src/app/module/chat/domain/use-case/socket/socket-message-update-use-case.service.ts +++ b/src/app/module/chat/domain/use-case/socket/socket-message-update-use-case.service.ts @@ -49,7 +49,7 @@ export class SocketMessageUpdateUseCaseService { messageToSave.sending = false if(result.isOk() && result.value) { - + tracing?.addEvent("Message found") const updateResult = await this.messageLocalDataSourceService.update(result.value.$id, messageToSave) tracing.setAttribute('outcome', 'success') diff --git a/src/app/ui/chat/component/messages/messages.page.html b/src/app/ui/chat/component/messages/messages.page.html index 549047c45..95da09682 100644 --- a/src/app/ui/chat/component/messages/messages.page.html +++ b/src/app/ui/chat/component/messages/messages.page.html @@ -72,10 +72,16 @@
+ diff --git a/src/app/ui/chat/component/messages/messages.page.ts b/src/app/ui/chat/component/messages/messages.page.ts index 042d3ca35..388934e32 100644 --- a/src/app/ui/chat/component/messages/messages.page.ts +++ b/src/app/ui/chat/component/messages/messages.page.ts @@ -320,10 +320,7 @@ export class MessagesPage implements OnInit, OnChanges, AfterViewInit, OnDestroy // } // return true; // Keep messages without an id // }); - - - messages = messages.sort((a: any, b: any) => a.$createAt - b.$createAt) - + console.time("mappingTime"); for(const message of messages) { const date = whatsappDate(message.sentAt, false) if(!this.date[date]) { @@ -334,12 +331,12 @@ export class MessagesPage implements OnInit, OnChanges, AfterViewInit, OnDestroy allMessage.push(new MessageViewModal(message)) } + console.timeEnd("mappingTime"); - console.time("mappingTime"); this.messages1[this.roomId] = allMessage - console.timeEnd("mappingTime"); + // if(messages.length >= 1) { // this.messages1[this.roomId].push(LastMessage) @@ -370,13 +367,15 @@ export class MessagesPage implements OnInit, OnChanges, AfterViewInit, OnDestroy for(const message of this.messages1[this.roomId]) { if(message.hasAttachment && message.attachments[0].source != MessageAttachmentSource.Webtrix) { - if(message.$id) { - this.chatServiceService.getMessageAttachmentByMessageId(message).then((result)=> { - if(result.isOk()) { - message.attachments[0].safeFile = result.value + this.chatServiceService.getMessageAttachmentByMessageId(message).then((result)=> { + if(result.isOk()) { + message.attachments[0].safeFile = result.value + + if(result.value.startsWith('blob:http')) { + message.attachments[0].blobURl = true } - }) - } + } + }) } } @@ -437,26 +436,34 @@ export class MessagesPage implements OnInit, OnChanges, AfterViewInit, OnDestroy listenToIncomingMessage() { this.messageReceiveSubject?.unsubscribe(); - this.messageReceiveSubject = this.chatServiceService.listenToIncomingMessage(this.roomId).subscribe(async (message) => { + this.messageReceiveSubject = this.chatServiceService.listenToIncomingMessage(this.roomId).subscribe(async (_message) => { - const date = whatsappDate(message.sentAt, false) + const date = whatsappDate(_message.sentAt, false) if(!this.date[date]) { this.date[date] = true - const Ballon = XBallon(message) + const Ballon = XBallon(_message) this.messages1[this.roomId].push(Ballon) } + const message = new MessageViewModal(_message) this.messages1[this.roomId].push(new MessageViewModal(message)) + console.log('message=======', message) + if(message.hasAttachment) { const result = await this.chatServiceService.downloadMessageAttachmentByMessageId({ - $messageId: message.$id, + $messageId: message.id, id: message.attachments[0].id }) - if(result.isOk()){ + if(result.isOk()) { message.attachments[0].safeFile = result.value + if((result.value as unknown as string).startsWith('blob:http')) { + message.attachments[0].blobURl = true + } else { + message.attachments[0].blobURl = false + } } } diff --git a/src/app/ui/chat/modal/view-onces/view-onces.module.ts b/src/app/ui/chat/modal/view-onces/view-onces.module.ts index ecbdf9531..1d3a6125c 100644 --- a/src/app/ui/chat/modal/view-onces/view-onces.module.ts +++ b/src/app/ui/chat/modal/view-onces/view-onces.module.ts @@ -7,13 +7,15 @@ import { IonicModule } from '@ionic/angular'; import { ViewOncesPageRoutingModule } from './view-onces-routing.module'; import { ViewOncesImagePage } from './view-onces.page'; +import { PipesModule } from 'src/app/pipes/pipes.module'; @NgModule({ imports: [ CommonModule, FormsModule, IonicModule, - ViewOncesPageRoutingModule + ViewOncesPageRoutingModule, + PipesModule ], declarations: [ViewOncesImagePage] }) diff --git a/src/app/ui/chat/modal/view-onces/view-onces.page.html b/src/app/ui/chat/modal/view-onces/view-onces.page.html index a336e8e1d..0e651658b 100644 --- a/src/app/ui/chat/modal/view-onces/view-onces.page.html +++ b/src/app/ui/chat/modal/view-onces/view-onces.page.html @@ -11,6 +11,14 @@
- + + +
diff --git a/src/app/ui/chat/modal/view-onces/view-onces.page.ts b/src/app/ui/chat/modal/view-onces/view-onces.page.ts index eb0d6d3a0..21bd372c9 100644 --- a/src/app/ui/chat/modal/view-onces/view-onces.page.ts +++ b/src/app/ui/chat/modal/view-onces/view-onces.page.ts @@ -19,19 +19,22 @@ export type ViewOncesImagePageInput = z.infer export class MessageViewModal { - $id?: number + $id?: string id?: string roomId?: string receiverId?: number @@ -13,15 +41,15 @@ export class MessageViewModal { oneShot: boolean = false sentAt?: string requireUnlock: boolean = false - info: typeof MessageEntitySchema._type.info = [] - sender!: typeof MessageEntitySchema._type.sender + info: typeof MessageViewModalSchema._type.info = [] + sender!: typeof MessageViewModalSchema._type.sender sending: boolean = false sendAttemp = 0 messageType = IMessageType.normal - attachments: typeof MessageEntitySchema._type.attachments = [] - reactions: typeof MessageEntitySchema._type.reactions = [] + attachments: typeof MessageViewModalSchema._type.attachments = [] + reactions: typeof MessageViewModalSchema._type.reactions = [] requestId: string - isDeleted: typeof MessageEntitySchema._type.isDeleted = false + isDeleted: typeof MessageViewModalSchema._type.isDeleted = false status!: 'allViewed' | 'allReceived'| 'enviado'| 'enviar' messageUiType!: 'info-meeting'| 'my-message'| 'other-message' diff --git a/src/app/ui/chat/store/roomStore.ts b/src/app/ui/chat/store/roomStore.ts new file mode 100644 index 000000000..7cda3c435 --- /dev/null +++ b/src/app/ui/chat/store/roomStore.ts @@ -0,0 +1,53 @@ +import { Injectable } from '@angular/core'; +import { MessageViewModal } from './model/message'; +import { MessageLocalDataSourceService } from 'src/app/module/chat/data/repository/message/message-local-data-source.service'; +import { whatsappDate } from '../../shared/utils/whatappdate'; +import { XBallon } from '../utils/messageBallon'; + +@Injectable({ + providedIn: 'root' +}) +export class RoomStore { + + roomId: string; + date: {[key: string]: Object} = {} + + constructor( + private messageLocalDataSourceService: MessageLocalDataSourceService, + ) {} + + openRoom() { + + } + messages1: {[key: string]: MessageViewModal[]} = {} + + async getMessages() { + + // dont remove this line + this.messages1[this.roomId] = [] + let messages = await this.messageLocalDataSourceService.getItems(this.roomId) + + this.messages1[this.roomId] = [] + this.date = {} + const allMessage = []; + + console.time("mappingTime"); + for(const message of messages) { + const date = whatsappDate(message.sentAt, false) + if(!this.date[date]) { + this.date[date] = true + const Ballon = XBallon(message) + allMessage.push(Ballon) + } + + allMessage.push(new MessageViewModal(message)) + } + console.timeEnd("mappingTime"); + + this.messages1[this.roomId] = allMessage + + // if(messages.length >= 1) { + // this.messages1[this.roomId].push(LastMessage) + // } + } +} diff --git a/src/app/ui/chat/utils/messageBallon.ts b/src/app/ui/chat/utils/messageBallon.ts index 280eda5e1..224ee9cbd 100644 --- a/src/app/ui/chat/utils/messageBallon.ts +++ b/src/app/ui/chat/utils/messageBallon.ts @@ -5,7 +5,7 @@ import { IMessageType } from "src/app/core/chat/entity/message"; export function XBallon(message: any) { const MessageBallon = new MessageViewModal(message as any) - MessageBallon.$id = 0 + MessageBallon.$id = '' MessageBallon.id = '' MessageBallon.isDeleted = false MessageBallon.sentAt = message.sentAt