diff --git a/src/app/core/chat/entity/message.ts b/src/app/core/chat/entity/message.ts index a37a5947e..a56d1d858 100644 --- a/src/app/core/chat/entity/message.ts +++ b/src/app/core/chat/entity/message.ts @@ -72,7 +72,7 @@ export type IMessage = z.infer; export class MessageEntity { - $id?: number + $id?: string id?: string roomId?: string receiverId?: number diff --git a/src/app/core/chat/repository/member/member-local-repository.ts b/src/app/core/chat/repository/member/member-local-repository.ts index 6643a0301..155c50bc9 100644 --- a/src/app/core/chat/repository/member/member-local-repository.ts +++ b/src/app/core/chat/repository/member/member-local-repository.ts @@ -3,7 +3,7 @@ import { Observable as DexieObservable, PromiseExtended } from 'Dexie'; import { AttachmentTable } from "src/app/infra/database/dexie/instance/chat/schema/attachment"; import { Result } from "neverthrow"; import { MemberTable, MemberTableSchema } from "src/app/infra/database/dexie/instance/chat/schema/members"; -import { RepositoryResult, RepositoryResultNew } from "src/app/infra/repository"; +import { RepositoryResult } from "src/app/infra/repository"; import { z } from "zod"; import { MemberListUPdateStatusInputDTO } from "src/app/module/chat/domain/use-case/socket/member-list-update-status-use-case.service"; import { Observable } from "rxjs"; @@ -26,7 +26,7 @@ export type IGetMemberLive = z.infer export abstract class IMemberLocalRepository extends DexieRepository { abstract directMember(input:IDirectMemberInput): Promise> - abstract addMember(data: MemberTable): Promise> + abstract addMember(data: MemberTable): Promise> abstract updateMemberRole(data: MemberTable): Promise> abstract updateMembersStatus(data: MemberListUPdateStatusInputDTO): Promise> abstract allMemberOnline(roomId:string): DexieObservable 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 1451b3e99..3205e34c9 100644 --- a/src/app/infra/database/dexie/instance/chat/schema/attachment.ts +++ b/src/app/infra/database/dexie/instance/chat/schema/attachment.ts @@ -5,7 +5,7 @@ import { MessageAttachmentFileType, MessageAttachmentSource } from "src/app/core export const AttachmentTableSchema = z.object({ $id: z.number().optional(), // local id - $messageId: z.number(), + $messageId: z.string(), attachmentId: z.string().optional(), file: z.instanceof(Blob), base64: zodDataUrlSchema.nullable().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 399838985..db85304b7 100644 --- a/src/app/infra/database/dexie/instance/chat/schema/message.ts +++ b/src/app/infra/database/dexie/instance/chat/schema/message.ts @@ -3,7 +3,8 @@ import { MessageAttachmentFileType, MessageAttachmentSource } from 'src/app/core import { z } from 'zod'; export const MessageTableSchema = z.object({ - $id: z.number().optional(), + $id: z.string().optional(), + $createAt: z.number().optional(), id: z.string().uuid().optional(), roomId: z.string().uuid().optional(), message: z.string().nullable().optional(), @@ -48,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' diff --git a/src/app/infra/database/dexie/service.ts b/src/app/infra/database/dexie/service.ts index c39742712..e07c27bc1 100644 --- a/src/app/infra/database/dexie/service.ts +++ b/src/app/infra/database/dexie/service.ts @@ -13,7 +13,7 @@ import { BoldTableColumn, DexieBoldTable } from './instance/chat/schema/bold'; // Database declaration (move this to its own module also) -export const chatDatabase = new Dexie('chat-database-infra',{ +export const chatDatabase = new Dexie('chat-database-v1',{ // indexedDB: new FDBFactory, // IDBKeyRange: FDBKeyRange, // Mocking IDBKeyRange }) as Dexie & { diff --git a/src/app/infra/repository/adapter.ts b/src/app/infra/repository/adapter.ts index fbdc6cb51..ca2c158cc 100644 --- a/src/app/infra/repository/adapter.ts +++ b/src/app/infra/repository/adapter.ts @@ -3,13 +3,12 @@ import { ZodError} from 'zod'; import { IDBError } from './types'; // Define a type for the Result of repository operations -export type RepositoryResult = Result>; -export type RepositoryResultNew = Result>; +export type RepositoryResult = Result>; export abstract class IDexieRepository { - abstract insert(document: T): Promise> + abstract insert(document: T): Promise> - abstract insertMany(documents: T[]): Promise>> + abstract insertMany(documents: T[]): Promise> abstract update(id: any, updatedDocument: Partial) : Promise> diff --git a/src/app/infra/repository/dexie/dexie-repository.service.ts b/src/app/infra/repository/dexie/dexie-repository.service.ts index 1f4717827..7abc9cd8c 100644 --- a/src/app/infra/repository/dexie/dexie-repository.service.ts +++ b/src/app/infra/repository/dexie/dexie-repository.service.ts @@ -2,15 +2,15 @@ import { Result, ok, err } from 'neverthrow'; import { EntityTable } from 'Dexie'; import { ZodError, ZodObject, ZodSchema } from 'zod'; import { Logger } from 'src/app/services/logger/main/service'; -import { IDexieRepository, RepositoryResultNew } from '../adapter' -import { IDBError } from '../types'; +import { IDexieRepository, RepositoryResult } from '../adapter' +import { IDBError, IDexieError } from '../types'; // Define a type for the Result of repository operations class DBError extends Error implements IDBError { zodError?: ZodError; parameters: T; - error?: any; + error?: IDexieError; constructor(data: IDBError) { super(data.message); @@ -18,6 +18,11 @@ class DBError extends Error implements IDBError { this.parameters = data.parameters; this.error = data.error; + Logger.error(data.message, { + zodError: this.zodError, + parameters: this.parameters + }) + // // Manually capture the stack trace if needed // if (Error.captureStackTrace) { // Error.captureStackTrace(this, DBError); @@ -25,9 +30,6 @@ class DBError extends Error implements IDBError { } } - -type RepositoryResult = Result>; - export class DexieRepository implements IDexieRepository { private table: EntityTable; private ZodSchema: ZodSchema @@ -39,7 +41,7 @@ export class DexieRepository implements IDexieRepository { this.ZodPartialSchema = (ZodSchema as ZodObject).partial() as any; } - async insert(document: T): Promise> { + async insert(document: T): Promise> { const dataValidation = this.ZodSchema.safeParse(document) @@ -47,19 +49,15 @@ export class DexieRepository implements IDexieRepository { try { const id = await this.table.add(dataValidation.data); return ok(id); - } catch (error) { - console.log(error) + } catch (_error) { + const error: IDexieError = _error return err(new DBError({ - message: `dexie.js failed to insert into ${this.table.name}`, + message: `dexie.js failed to insert into ${this.table.name}, ${error.message}`, parameters: document, error: error })) } } else { - Logger.error(`dexie.js failed to insert into ${this.table.name}, invalid data`, { - data: document, - zodError: dataValidation.error.issues - }); return err(new DBError({ message: `dexie.js failed to insert into ${this.table.name}, invalid data`, parameters: document, @@ -68,27 +66,29 @@ export class DexieRepository implements IDexieRepository { } } - async insertMany(documents: T[]): Promise>> { + async insertMany(documents: T[]): Promise> { // Validate each document const schema = this.ZodSchema.array() const validationResult = schema.safeParse(documents) if(!validationResult.success) { - Logger.error(`dexie.js failed to insert many into ${this.table.name}, invalid data`, { - data: document, - zodError: validationResult.error.issues - }); - return err((validationResult as unknown as ZodError)) + return err(new DBError({ + message: `dexie.js failed to insert many into ${this.table.name}, invalid data`, + parameters: documents, + zodError: validationResult.error + })) } try { const ids = await this.table.bulkAdd(documents as any); return ok(ids); - } catch (error) { - Logger.error(`dexie.js failed to insert many into ${this.table.name}`, { - data: document - }); - return err(new Error('Failed to insert multiple documents: ' + error.message)); + } catch (_error) { + const error: IDexieError = _error + return err(new DBError({ + message: `dexie.js failed to insert into many ${this.table.name}, ${error.message}`, + parameters: documents, + error: error + })) } } @@ -100,19 +100,26 @@ export class DexieRepository implements IDexieRepository { try { const updatedCount = await this.table.update(id, dataValidation.data); return ok(updatedCount); - } catch (error) { - Logger.error(`dexie.js failed to update into ${this.table.name}, invalid data`, { - data: updatedDocument, - id, - }); - return err(new Error('Failed to update document: ' + error.message)); + } catch (_error) { + const error: IDexieError = _error + return err(new DBError({ + message: `dexie.js Failed to update into ${this.table.name}, ${error.message} `, + parameters: { + ...updatedDocument, + [this.table.schema.primKey.name]: id + } as unknown as T, + error: error + })) } } else { - Logger.error(`dexie.js failed to update into ${this.table.name}, invalid data`, { - data: document, - zodError: dataValidation.error.issues - }); - return err((dataValidation as unknown as ZodError)) + return err(new DBError({ + message: `dexie.js failed to update into ${this.table.name}, invalid data`, + parameters: { + ...updatedDocument, + [this.table.schema.primKey.name]: id + } as unknown as T, + zodError: dataValidation.error + })) } } @@ -125,15 +132,20 @@ export class DexieRepository implements IDexieRepository { try { const updatedCount = await this.table.bulkPut(dataValidation.data); return ok(updatedCount); - } catch (error) { - return err(new Error('Failed to update document: ' + error.message)); + } catch (_error) { + const error: IDexieError = _error + return err(new DBError({ + message: `dexie.js Failed to update into ${this.table.name}, ${error.message} `, + parameters: document, + error: error + })) } } else { - Logger.error(`dexie.js failed to update many into ${this.table.name}, invalid data`, { - data: document, - zodError: dataValidation.error.issues - }); - return err((dataValidation as unknown as ZodError)) + return err(new DBError({ + message: `dexie.js failed to update many into ${this.table.name}, invalid data`, + parameters: updatedDocument , + zodError: dataValidation.error + })) } } @@ -142,8 +154,13 @@ export class DexieRepository implements IDexieRepository { try { await this.table.delete(id); return ok(undefined); - } catch (error) { - return err(new Error('Failed to delete document: ' + error.message)); + } catch (_error) { + const error: IDexieError = _error + return err(new DBError({ + message: `dexie.js Failed to delete into ${this.table.name}, ${error.message} `, + parameters: id, + error: error + })) } } @@ -151,8 +168,13 @@ export class DexieRepository implements IDexieRepository { try { const document = await this.table.get(id); return ok(document); - } catch (error) { - return err(new Error('Failed to find document by ID: ' + error.message)); + } catch (_error) { + const error: IDexieError = _error + return err(new DBError({ + message: `dexie.js Failed to delete into ${this.table.name}, ${error.message} `, + parameters: id, + error: error + })) } } @@ -160,11 +182,13 @@ export class DexieRepository implements IDexieRepository { try { const documents: any = await this.table.where(filter).toArray(); return ok(documents); - } catch (error) { - Logger.error(`dexie.js failed to find into ${this.table.name}`, { - data: filter, - }); - return err(new Error('Failed to find documents: ' + error.message)); + } catch (_error) { + const error: IDexieError = _error; + return err(new DBError({ + message: `dexie.js Failed to find into ${this.table.name}, ${error.message} `, + parameters: filter as any, + error: error + })) } } @@ -172,12 +196,13 @@ export class DexieRepository implements IDexieRepository { try { const document = await this.table.where(filter).first(); return ok(document); - } catch (error) { - Logger.error(`dexie.js failed to findOne into ${this.table.name}`, { - data: filter, - error - }); - return err(new Error('Failed to find document: ' + error.message)); + } catch (_error) { + const error: IDexieError = _error + return err(new DBError({ + message: `dexie.js Failed to findOne into ${this.table.name}, ${error.message} `, + parameters: filter as any, + error: error + })) } } @@ -185,8 +210,13 @@ export class DexieRepository implements IDexieRepository { try { const documents = await this.table.toArray() return ok(documents); - } catch (error) { - return err(new Error('Failed to retrieve all documents: ' + error.message)); + } catch (_error) { + const error: IDexieError = _error + return err(new DBError({ + message: `dexie.js Failed to findAll into ${this.table.name}, ${error.message} `, + parameters: null, + error: error + })) } } @@ -194,8 +224,13 @@ export class DexieRepository implements IDexieRepository { try { const count = filter ? await this.table.where(filter).count() : await this.table.count(); return ok(count); - } catch (error) { - return err(new Error('Failed to count documents: ' + error.message)); + } catch (_error) { + const error: IDexieError = _error + return err(new DBError({ + message: `dexie.js Failed to count into ${this.table.name}, ${error.message}`, + parameters: filter as any, + error: error + })) } } } diff --git a/src/app/infra/repository/types.ts b/src/app/infra/repository/types.ts index de8b11c47..f623814c0 100644 --- a/src/app/infra/repository/types.ts +++ b/src/app/infra/repository/types.ts @@ -37,10 +37,20 @@ import { ZodError } from "zod" // command: DatabaseOperationEnum; // }; - export type IDBError = { message: string, zodError?: ZodError, parameters?: T, - error?: any + error?: IDexieError +} + +export interface IDexieError extends Error { + name: string; // The name of the error, e.g., 'NotFoundError', 'ConstraintError' + message: string; // The error message + stack?: string; // Optional stack trace + inner?: Error; // Some Dexie errors have an inner error + dbName?: string; // The name of the Dexie database where the error occurred + tableName?: string; // The name of the table where the error occurred + operation?: string; // The operation being performed (e.g., 'put', 'get', 'delete') + key?: any; // The key involved in the operation, if applicable } 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 b716e064d..bf12cd49c 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 @@ -8,6 +8,7 @@ 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'; import { filter, map } from 'rxjs/operators'; +import { v4 as uuidv4 } from 'uuid' @Injectable({ providedIn: 'root' @@ -15,6 +16,7 @@ import { filter, map } from 'rxjs/operators'; export class MessageLocalDataSourceService extends DexieRepository implements IMessageLocalRepository { private creatingSubject : BehaviorSubject = new BehaviorSubject(null); + private lastTimestamp = 0; constructor() { super(chatDatabase.message, MessageTableSchema) @@ -26,6 +28,23 @@ export class MessageLocalDataSourceService extends DexieRepository { + let now = Date.now(); + + // If the current time is the same as the last, increment + if (now <= this.lastTimestamp) { + obj.$createAt = this.lastTimestamp + 1; + this.lastTimestamp = this.lastTimestamp + 1; + } else { + this.lastTimestamp = now; + obj.$createAt = now; + } + + if(obj.id) { + obj.$id = obj.id + } else { + obj.$id = 'Local-'+uuidv4() + } + this.creatingSubject.next(obj) }); } 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 85c2e74c3..80ab5baa7 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 @@ -6,7 +6,7 @@ import { AttachmentLocalDataSource } from 'src/app/module/chat/data/repository/a import { z } from 'zod'; const DownloadMessageAttachmentByMessageIdSchema = z.object({ - $messageId: z.number(), + $messageId: z.string(), id: z.string() }) @@ -30,7 +30,7 @@ export class DownloadMessageAttachmentUserCaseService { const dataUrl = await createBlobUrl(blob) if(dataUrl.isOk()) { - + Logger.info('downloaded file #1', { // data: dataUrl.slice(0, 100)+'...', context: 'DownloadMessageAttachmentUserCaseService' diff --git a/src/app/module/chat/domain/use-case/room/room-bold-sync-use-case.service.ts b/src/app/module/chat/domain/use-case/room/room-bold-sync-use-case.service.ts index 1e2eaac8d..0deb27dbb 100644 --- a/src/app/module/chat/domain/use-case/room/room-bold-sync-use-case.service.ts +++ b/src/app/module/chat/domain/use-case/room/room-bold-sync-use-case.service.ts @@ -90,7 +90,7 @@ export class RoomBoldSyncUseCaseService { await this.boldLocalRepository.open() const result = await this.boldLocalRepository.findOne({roomId: newMessage.roomId}) - if(result.isOk() && !result.value) { + if(result.isOk() && !result.value?.bold) { const result = await this.boldLocalRepository.insert({roomId: newMessage.roomId, bold: 1}) } else if(result.isOk() && result.value.bold == 0) { const result = await this.boldLocalRepository.update(newMessage.roomId, {bold: 1}) diff --git a/src/app/ui/chat/component/messages/messages.page.html b/src/app/ui/chat/component/messages/messages.page.html index 122b88d40..549047c45 100644 --- a/src/app/ui/chat/component/messages/messages.page.html +++ b/src/app/ui/chat/component/messages/messages.page.html @@ -154,7 +154,7 @@
{{ message.message }}
-
+
{{ reaction.reaction }} diff --git a/src/app/ui/chat/component/messages/messages.page.ts b/src/app/ui/chat/component/messages/messages.page.ts index 7b8dbbada..042d3ca35 100644 --- a/src/app/ui/chat/component/messages/messages.page.ts +++ b/src/app/ui/chat/component/messages/messages.page.ts @@ -307,6 +307,23 @@ export class MessagesPage implements OnInit, OnChanges, AfterViewInit, OnDestroy this.date = {} const allMessage = []; + // let ids = {} + // messages = messages.filter((message: any) => { + // if (message.$createAt) { + // if (!ids[message.$createAt]) { + // ids[message.$createAt] = true; + // return true; // Keep this message + // } else { + // console.log('delete'); + // return false; // Remove this message + // } + // } + // return true; // Keep messages without an id + // }); + + + messages = messages.sort((a: any, b: any) => a.$createAt - b.$createAt) + for(const message of messages) { const date = whatsappDate(message.sentAt, false) if(!this.date[date]) {