fix chat bold

This commit is contained in:
Peter Maquiran
2024-09-02 12:33:43 +01:00
parent 18463e43e4
commit 1b9b4600ab
18 changed files with 162 additions and 81 deletions
+5
View File
@@ -62,6 +62,7 @@ export const MessageEntitySchema = z.object({
safeFile: z.any().optional(),
description: z.string().nullable().optional()
})).optional(),
origin: z.enum(['history', 'local', 'incoming']).optional()
})
export type IMessage = z.infer<typeof MessageEntitySchema>;
@@ -99,4 +100,8 @@ export class MessageEntity {
return this.attachments.length >= 1
}
static haveSeen(info: typeof MessageEntitySchema._type.info) {
return info.filter(e => typeof e.readAt != 'string')
}
}
@@ -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 } from "src/app/infra/repository";
import { RepositoryResult, RepositoryResultNew } 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,15 +26,15 @@ export type IGetMemberLive = z.infer<typeof GetMemberLiveInput>
export abstract class IMemberLocalRepository extends DexieRepository<AttachmentTable, AttachmentTable> {
abstract directMember(input:IDirectMemberInput): Promise<Result<MemberTable, any>>
abstract addMember(data: MemberTable): Promise<RepositoryResult<number, MemberTable>>
abstract addMember(data: MemberTable): Promise<RepositoryResultNew<number, MemberTable>>
abstract updateMemberRole(data: MemberTable): Promise<Result<number, any>>
abstract updateMembersStatus(data: MemberListUPdateStatusInputDTO): Promise<Result<true, any>>
abstract allMemberOnline(roomId:string): DexieObservable<boolean>
abstract removeMemberFromRoom(roomId:string): Promise<Result<any ,any>>
abstract removeMemberFromRoom(roomId:string): Promise<Result<any ,any>>
abstract getMemberLive(input: IGetMemberLive): DexieObservable<MemberTable>
abstract getRoomMemberById(roomId:string): Promise<MemberTable[]>
abstract getRoomMemberByIdLive(roomId:string): Observable<MemberTable[]>
abstract getRoomMemberById(roomId:string): Promise<MemberTable[]>
abstract getRoomMemberByIdLive(roomId:string): Observable<MemberTable[]>
abstract getRoomMemberNoneAdminByIdLive(roomId:string): DexieObservable<MemberTable[]>
}
}
@@ -42,7 +42,8 @@ export const MessageTableSchema = z.object({
id: z.string().optional(),
description: z.string().nullable().optional(),
mimeType: z.string().optional()
})).optional()
})).optional(),
origin: z.enum(['history', 'local', 'incoming']).optional()
})
export type MessageTable = z.infer<typeof MessageTableSchema>
+9 -8
View File
@@ -1,26 +1,27 @@
import { Result } from 'neverthrow';
import { ZodError} from 'zod';
import { IDBError } from './types';
// Define a type for the Result of repository operations
export type RepositoryResult<T, E> = Result<T, Error | ZodError<E>>;
export type RepositoryResultNew<T, E> = Result<T, IDBError<E>>;
export abstract class IDexieRepository<T, R> {
abstract insert(document: T): Promise<RepositoryResult<number, T>>
abstract insert(document: T): Promise<RepositoryResultNew<number, T>>
abstract insertMany(documents: T[]): Promise<RepositoryResult<number[], ZodError<T>>>
abstract update(id: any, updatedDocument: Partial<T>) : Promise<RepositoryResult<number, T>>
abstract update(id: any, updatedDocument: Partial<T>) : Promise<RepositoryResult<number, T>>
abstract delete(id: any): Promise<RepositoryResult<void, T>>
abstract delete(id: any): Promise<RepositoryResult<void, T>>
abstract findById(id: any) : Promise<RepositoryResult<R, any>>
abstract find(filter: Partial<T>): Promise<RepositoryResult<R[], T[]>>
abstract find(filter: Partial<T>): Promise<RepositoryResult<R[], T[]>>
abstract findOne(filter: Partial<T>): Promise<RepositoryResult<T | undefined, T>>
abstract findOne(filter: Partial<T>): Promise<RepositoryResult<T | undefined, T>>
abstract findAll(): Promise<RepositoryResult<T[], T>>
abstract findAll(): Promise<RepositoryResult<T[], T>>
abstract count(filter?: Object): Promise<RepositoryResult<number, T>>
abstract count(filter?: Object): Promise<RepositoryResult<number, T>>
}
@@ -2,9 +2,30 @@ 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 } from '../adapter'
import { IDexieRepository, RepositoryResultNew } from '../adapter'
import { IDBError } from '../types';
// Define a type for the Result of repository operations
class DBError<T> extends Error implements IDBError<T> {
zodError?: ZodError<T>;
parameters: T;
error?: any;
constructor(data: IDBError<T>) {
super(data.message);
this.zodError = data.zodError;
this.parameters = data.parameters;
this.error = data.error;
// // Manually capture the stack trace if needed
// if (Error.captureStackTrace) {
// Error.captureStackTrace(this, DBError);
// }
}
}
type RepositoryResult<T, E> = Result<T, Error | ZodError<E>>;
export class DexieRepository<T, R> implements IDexieRepository<T, R> {
@@ -18,7 +39,7 @@ export class DexieRepository<T, R> implements IDexieRepository<T, R> {
this.ZodPartialSchema = (ZodSchema as ZodObject<any>).partial() as any;
}
async insert(document: T): Promise<RepositoryResult<number, T>> {
async insert(document: T): Promise<RepositoryResultNew<number, T>> {
const dataValidation = this.ZodSchema.safeParse(document)
@@ -28,14 +49,22 @@ export class DexieRepository<T, R> implements IDexieRepository<T, R> {
return ok(id);
} catch (error) {
console.log(error)
return err(new Error('Failed to insert document: ' + error));
return err(new DBError({
message: `dexie.js failed to insert into ${this.table.name}`,
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((dataValidation as unknown as ZodError<T>))
return err(new DBError({
message: `dexie.js failed to insert into ${this.table.name}, invalid data`,
parameters: document,
zodError: dataValidation.error
}))
}
}
+10
View File
@@ -6,6 +6,8 @@
// upsertedCount: number;
// };
import { ZodError } from "zod"
// export type RemovedModel = {
// deletedCount: number;
// deleted: boolean;
@@ -34,3 +36,11 @@
// value: unknown[];
// command: DatabaseOperationEnum;
// };
export type IDBError<T> = {
message: string,
zodError?: ZodError<T>,
parameters?: T,
error?: any
}
-2
View File
@@ -36,7 +36,6 @@ import { DistributionLocalRepository } from './data/repository/destribution/dest
import { DistributionService } from './domain/service/distribution.service'
import { BoldLocalRepository } from './data/repository/bold/bold-local-repository';
import { IBoldLocalRepository } from 'src/app/core/chat/repository/bold/bold-local-repository';
import { BoldService } from 'src/app/module/chat/domain/service/bold.service'
import { RoomLastMessageService } from 'src/app/module/chat/domain/service/room-last-message.service'
@NgModule({
imports: [HttpModule],
@@ -119,7 +118,6 @@ export class ChatModule {
private UserTypingRemoteRepositoryService: UserTypingRemoteRepositoryService,
private RoomService: RoomService,
private DistributionService: DistributionService,
private BoldService: BoldService,
private RoomLastMessageService: RoomLastMessageService
) {
@@ -6,14 +6,15 @@ import { Observable as DexieObservable, PromiseExtended } from 'Dexie';
import { 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 { combineLatest, from, Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { BehaviorSubject, combineLatest, from, Observable } from 'rxjs';
import { filter, map } from 'rxjs/operators';
@Injectable({
providedIn: 'root'
})
export class MessageLocalDataSourceService extends DexieRepository<MessageTable, MessageEntity> implements IMessageLocalRepository {
private creatingSubject : BehaviorSubject<MessageTable> = new BehaviorSubject<MessageTable>(null);
constructor() {
super(chatDatabase.message, MessageTableSchema)
@@ -21,6 +22,20 @@ export class MessageLocalDataSourceService extends DexieRepository<MessageTable,
this.setAllSenderToFalse();
}
private onCreatingHook() {
chatDatabase.message.hook('creating', (primaryKey, obj, transaction) => {
console.log('A new friend is being added:', obj);
});
}
// onCreateObservable() {
// return this.creatingSubject.asObservable().pipe(
// filter(e => e?.sender?.wxFullName)
// )
// }
async setAllSenderToFalse() {
try {
await chatDatabase.transaction('rw', chatDatabase.message, async () => {
@@ -40,7 +40,7 @@ import { MessageSocketRepositoryService } from 'src/app/module/chat/data/reposit
import { MessageMarkAsReadInput } from "src/app/module/chat/domain/use-case/message/message-mark-as-read-use-case.service";
import { BoldRemoveByRoomIdInput, BoldRemoveByRoomIdService } from 'src/app/module/chat/domain/use-case/bold/bold-remove-by-room-id.service';
import { MemberListHttpSyncUseCase } from 'src/app/module/chat/domain/use-case/member/member-list-http-sync-use-case.ts.service'
import { RoomSetLastMessageService } from 'src/app/module/chat/domain/use-case/room/room-set-last-message.service'
import { RoomBoldSyncUseCaseService } from 'src/app/module/chat/domain/use-case/room/room-bold-sync-use-case.service'
export const InstanceId = uuidv4();
@Injectable({
@@ -82,7 +82,7 @@ export class ChatServiceService {
private MessageSocketRepositoryService: MessageSocketRepositoryService,
private BoldRemoveByRoomIdService: BoldRemoveByRoomIdService,
private MemberListHttpSyncUseCase: MemberListHttpSyncUseCase,
private RoomSetLastMessageService: RoomSetLastMessageService
private RoomBoldSyncUseCaseService: RoomBoldSyncUseCaseService
) {
this.MessageSocketRepositoryService.listenToDeleteMessages()
.pipe()
@@ -1,16 +0,0 @@
import { TestBed } from '@angular/core/testing';
import { BoldService } from './bold.service';
describe('BoldService', () => {
let service: BoldService;
beforeEach(() => {
TestBed.configureTestingModule({});
service = TestBed.inject(BoldService);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
});
@@ -7,7 +7,8 @@ import { err, Result } from 'neverthrow';
import { Logger } from 'src/app/services/logger/main/service';
import { MessageEntity } from '../../../../../core/chat/entity/message';
import { AttachmentTableSchema } from 'src/app/infra/database/dexie/instance/chat/schema/attachment';
import { XTracerAsync } from 'src/app/services/monitoring/opentelemetry/tracer';
import { TracingType, XTracerAsync } from 'src/app/services/monitoring/opentelemetry/tracer';
import { isHttpResponse } from 'src/app/infra/http/http.service';
const MessageAttachmentByMessageIdSchema = AttachmentTableSchema.pick({
$messageId: true,
@@ -27,7 +28,7 @@ export class MessageAttachmentByMessageIdUseCase {
) { }
@XTracerAsync({name:'Message-Attachment-By-MessageIdUseCase', module:'chat', bugPrint: true, waitNThrow: 15000})
async execute(input: MessageEntity): Promise<Result<string, any>> {
async execute(input: MessageEntity, tracing?: TracingType): Promise<Result<string, any>> {
const getLocalAttachment = await this.AttachmentLocalDataSource.findOne({
$messageId: input.$id
@@ -47,7 +48,7 @@ export class MessageAttachmentByMessageIdUseCase {
} else {
// has data url
return getLocalAttachment.map((e) => {
// Logger.info('restored file .', {
// data: e.base64.slice(0, 100)+'...'
// })
@@ -56,18 +57,22 @@ export class MessageAttachmentByMessageIdUseCase {
})
}
} else {
console.log('donwload')
const result = await this.AttachmentRemoteDataSourceService.getAttachment(input.attachments[0].id)
} else {
tracing.setAttribute('download', 'true')
const result = await this.AttachmentRemoteDataSourceService.getAttachment(input.attachments[0].id)
tracing.setAttribute('attachmentId', input.attachments[0].id.toString())
if(result.isErr()) {
Logger.error('failed to download message attachment', {
tracing.hasError('failed to download message attachment', {
error: result.error,
data: 'document id '+ input.attachments[0].id,
messageId: input.id,
$messageId: input.$id
})
if(isHttpResponse(result.error)) {
tracing.setAttribute('attachmentUrl', result.error.message)
}
}
@@ -79,9 +84,9 @@ export class MessageAttachmentByMessageIdUseCase {
if(dataUrl.isOk()) {
//console.log('done convert')
Logger.info('downloaded file .', {
//Logger.info('downloaded file .', {
// data: dataUrl.value.slice(0, 100)+'...'
})
//})
this.AttachmentLocalDataSource.insert({
$messageId: input.$id,
@@ -94,7 +99,7 @@ export class MessageAttachmentByMessageIdUseCase {
mimeType: input.attachments[0].mimeType,
}).then((e) => {
if(e.isErr()) {
Logger.error('failed to create attachment locally on send message', {
tracing.hasError('failed to create attachment locally on send message', {
error: e.error,
// data: dataUrl.value.slice(0, 100)+'...'
})
@@ -214,7 +214,7 @@ export class MessageCreateUseCaseService {
} else {
Logger.error('failed to insert locally', {
error: createMessageLocally.error
error: createMessageLocally.error.message
})
}
} else {
@@ -41,19 +41,19 @@ export class SyncAllRoomMessagesService {
this.messageRemoteDataSourceService.getMessagesFromRoom(room.id),
this.messageLocalDataSourceService.getItems(room.id)
]);
tracing.addEvent('async n ' + n);
n++;
if (result.isOk()) {
const { addedItems, changedItems, deletedItems } = messageListDetermineChanges(result.value.data, localResult);
for (const message of changedItems) {
let clone: MessageTable = { ...message, roomId: room.id };
await this.messageLocalDataSourceService.update(clone.$id, clone);
const me = message.info.find(e => e.memberId === SessionStore.user.UserId && typeof e.deliverAt === 'string');
if (!me) {
this.MessageSocketRepositoryService.sendDeliverAt({
memberId: SessionStore.user.UserId,
@@ -61,22 +61,25 @@ export class SyncAllRoomMessagesService {
roomId: message.roomId,
requestId: uuidv4()
});
tracing.addEvent('send deliver roomId ' + room.id);
}
}
for (const message of addedItems) {
let clone: MessageTable = { ...message, roomId: room.id };
// You can perform operations with addedItems here if needed
}
await this.messageLocalDataSourceService.insertMany(addedItems.reverse());
await this.messageLocalDataSourceService.insertMany(addedItems.reverse().map(e => {
e.origin = 'history'
return e
}));
} else {
Logger.error('failed to get room message ' + room.id);
}
});
// Wait for all the promises to resolve
await Promise.all(roomPromises);
@@ -1,28 +1,33 @@
import { Injectable } from '@angular/core';
import { filter, map } from 'rxjs/operators';
import { IMessageSocketRepository } from 'src/app/core/chat/repository/message/message-socket-repository';
import { InstanceId } from '../chat-service.service';
import { MessageEntity } from 'src/app/core/chat/entity/message';
import { IBoldLocalRepository } from 'src/app/core/chat/repository/bold/bold-local-repository';
import { InstanceId } from '../../chat-service.service';
import { IMessageGetAllByRoomIdOutPut } from 'src/app/core/chat/usecase/message/message-get-all-by-room-Id';
import { HttpAdapter } from 'src/app/infra/http/adapter';
@Injectable({
providedIn: 'root'
})
export class BoldService {
export class RoomBoldSyncUseCaseService {
constructor(
private MessageSocketRepositoryService: IMessageSocketRepository,
private boldLocalRepository: IBoldLocalRepository
private boldLocalRepository: IBoldLocalRepository,
private http: HttpAdapter,
) {
this.listenToIncomingMessage();
}
this.loadHistory()
}
listenToIncomingMessage() {
private listenToIncomingMessage() {
return this.MessageSocketRepositoryService.listenToMessages().pipe(
filter((message) => !message?.requestId?.startsWith(InstanceId)),
map(message => Object.assign(new MessageEntity(), message))
).subscribe(async (message) => {
const result = await this.boldLocalRepository.findOne({roomId: message.roomId})
if(result.isOk() && !result.value) {
@@ -32,6 +37,24 @@ export class BoldService {
}
});
}
private loadHistory() {
const regex = new RegExp("Room\\/([0-9a-fA-F]{8})-([0-9a-fA-F]{4})-([0-9a-fA-F]{4})-([0-9a-fA-F]{4})-([0-9a-fA-F]{12})\\/Messages");
return this.http.listen().pipe(
filter((response: any)=> {
return response?.isOk() && regex.test(response.value.url)
}),
map((response: any) => response.value.data as IMessageGetAllByRoomIdOutPut)
).subscribe(async (data)=> {
if(data.data.length >= 1) {
for(const message of data.data) {
const haveSeen = MessageEntity.haveSeen(message.info)
}
}
})
}
}
@@ -10,6 +10,7 @@ import { openTelemetryLogging } from './logging';
import {
SpanStatus, SpanStatusCode
} from '@opentelemetry/api';
import { Logger } from '../../logger/main/service';
const tracerInstance = OpentelemetryAgendaProvider.getTracer('example-tracer-hole', '111', {})
const tracerNotificationInstance = OpentelemetryNotificationProvider.getTracer('example-tracer-hole', '111', {})
const tracerChat = OpentelemetryChatProvider.getTracer('OpentelemetryChatProvider','some' ,{})
@@ -154,10 +155,13 @@ const createTracingInstance = ({bugPrint, name, module, autoFinish, waitNThrow =
finish = true
},
hasError:(message: string) => {
hasError:(message: string, obj:Object = {}) => {
const spanId = span.spanContext().spanId;
data.errors.push(message)
data.status = {code: SpanStatusCode.ERROR, message}
span.setStatus({code: SpanStatusCode.ERROR, message})
Logger.error(message, {...obj, spanId, name})
},
createSpan: (name, parent?: any) => {
return tracerInstance.startSpan(name, { root: false }, parent) as Span;
@@ -279,7 +283,7 @@ export type TracingType = {
getAttribute: (key: string) => string;
LocalLogEvent: (name: string, attributesOrStartTime: any, obj?:any) => void;
finish: () => void;
hasError:(message: string) => void;
hasError:(message: string, obj?: Object) => void;
createSpan:(name, parent?: any) => Span;
};
+3 -6
View File
@@ -13,16 +13,13 @@ import { TimeService } from 'src/app/services/functions/time.service';
import { ThemeService } from 'src/app/services/theme.service'
import { DataService } from 'src/app/services/data.service';
import { RouteService } from 'src/app/services/route.service';
// import { RoomRemoteDataSourceState } from 'src/app/module/chat/data/repository/room-memory-data-source';
import { Observable as DexieObservable } from 'Dexie';
import { EditGroupPage } from './modal/edit-group/edit-group.page';
import { ChatServiceService } from 'src/app/module/chat/domain/chat-service.service'
import { RoomLocalRepository } from 'src/app/module/chat/data/repository/room/room-local-repository.service'
import { map, switchMap, tap } from 'rxjs/operators';
import { map, tap } from 'rxjs/operators';
import { BehaviorSubject, interval, Subscription } from 'rxjs';
import { RoomTable } from 'src/app/infra/database/dexie/instance/chat/schema/room';
import { RoomEntity, RoomType } from 'src/app/core/chat/entity/group';
import { RoomType } from 'src/app/core/chat/entity/group';
import { BoldLocalRepository } from 'src/app/module/chat/data/repository/bold/bold-local-repository'
import { BoldTable } from 'src/app/infra/database/dexie/instance/chat/schema/bold';
import { RoomViewModel } from './store/model/room';
@@ -122,9 +119,9 @@ export class ChatPage implements OnInit {
new Date(b.messages?.[0]?.sentAt as string).getTime() -
new Date(a.messages?.[0]?.sentAt as string).getTime()
);
this.RoomSelected = this.rooms.filter(e => e.id == this.idSelected)[0]
// this.RoomSelected = this.rooms.filter(e => e.id == this.idSelected)[0]
}
ngOnInit() {
// this.subscription = this.roomListSubject.pipe(
@@ -3,6 +3,7 @@
<div class="main-header">
<div class="header-top">
<div class="middle" >
<ion-label class="title" *ngIf="(roomData$ | async) === null"> {{ room.roomName }}</ion-label>
<ion-label class="title" *ngIf="roomData$ | async as roomData"> {{ roomData.roomName }}</ion-label>
<!-- <button (click)="ChatMessageDebuggingPage()">Dev</button> -->
<span *ngIf="roomStatus$ | async as roomStatus"><ion-icon *ngIf="roomStatus" class="online" name="ellipse"></ion-icon></span>
@@ -22,9 +23,9 @@
<ion-icon *ngIf="ThemeService.currentTheme == 'gov' " src="assets/icon/theme/gov/icons-user.svg"></ion-icon>
</div>
<ion-list class="header-bottom-contacts" *ngIf="roomMembers$ | async as memberList">
<ion-list class="header-bottom-contacts" *ngIf="roomMembers$ | async as memberList" >
<ng-container *ngFor="let user of memberList; let i = index">
{{ user.wxFullName }}<ng-container *ngIf="i < memberList.length - 1">, </ng-container>
<span *ngIf="roomType == RoomTypeEnum.Group"> {{ user.wxFullName }}<ng-container *ngIf="i < memberList.length - 1">, </ng-container> </span>
</ng-container>
</ion-list>
@@ -50,6 +50,7 @@ import { RoomTable } from 'src/app/infra/database/dexie/instance/chat/schema/roo
import { TypingTable } from 'src/app/infra/database/dexie/instance/chat/schema/typing';
import { HttpClient } from '@angular/common/http';
import { v4 as uuidv4 } from 'uuid'
import { RoomViewModel } from '../../store/model/room';
@Component({
selector: 'app-messages',
templateUrl: './messages.page.html',
@@ -69,6 +70,7 @@ export class MessagesPage implements OnInit, OnChanges, AfterViewInit, OnDestroy
roomType!: RoomType
RoomTypeEnum = RoomType
@Input() room!: RoomViewModel
@Input() roomId: string;
@Input() showMessages: string;
@@ -112,7 +114,6 @@ export class MessagesPage implements OnInit, OnChanges, AfterViewInit, OnDestroy
@ViewChild('array') myInputRef!: ElementRef;
userName = "";
room: any = new Array();
roomName: any;
isAdmin = true;
roomCountDownDate: string;
@@ -276,6 +277,7 @@ export class MessagesPage implements OnInit, OnChanges, AfterViewInit, OnDestroy
this.chatServiceService.removeBoldFromRoom({roomId: this.roomId})
this.chatServiceService.getRoomById(this.roomId)
}
messageStatus(message: MessageEntity) {
@@ -423,7 +425,10 @@ export class MessagesPage implements OnInit, OnChanges, AfterViewInit, OnDestroy
this.scrollToBottomClicked()
}, 100)
this.chatServiceService.removeBoldFromRoom({roomId: this.roomId})
setTimeout(() => {
this.chatServiceService.removeBoldFromRoom({roomId: this.roomId})
}, 1000)
});
}