send direct message

This commit is contained in:
Peter Maquiran
2024-08-20 16:34:47 +01:00
parent 4fb24f7875
commit 59fc19879f
41 changed files with 912 additions and 308 deletions
+1 -1
View File
@@ -123,7 +123,7 @@ import { FirebaseX } from '@ionic-native/firebase-x/ngx'; */
],
beforeSend(event) {
if (event.level === 'error') {
console.log(event.exception.values[0].value)
// console.log(event.exception.values[0].value)
openTelemetryLogging.send({
type: 'graylog',
payload: {
@@ -0,0 +1,8 @@
export interface sendReadAtInput {
memberId: number,
messageId:string,
roomId: string,
requestId: string
}
+199 -199
View File
@@ -78,206 +78,206 @@ const routes: Routes = [
],
},
// {
// path: 'agenda',
// children: [
// {
// path:'',
// loadChildren: () => AgendaPageModule
// },
// {
// path:':eventId/:caller',
// loadChildren: () => import('../pages/agenda/view-event/view-event.module').then( m => m.ViewEventPageModule),
// },
// {
// path: 'eventId/:caller',
// loadChildren: () => import('../pages/agenda/view-event/view-event.module').then( m => m.ViewEventPageModule),
// },
// {
// path: 'edit-event',
// loadChildren: () => import('../pages/agenda/edit-event/edit-event.module').then( m => m.EditEventPageModule)
// },
// {
// path: 'emend-message-modal',
// loadChildren: () => import('../pages/gabinete-digital/event-list/approve-event-modal/approve-event-modal.module').then( m => m.ApproveEventModalPageModule)
// },
// {
// path: 'view-event',
// loadChildren: () => import('../pages/agenda/view-event/view-event.module').then( m => m.ViewEventPageModule)
// },
// {
// path:'event-list',
// children: [
// {
// path:'',
// loadChildren: ()=> import('../pages/gabinete-digital/event-list/event-list.module').then(m => m.EventListPageModule)
// },
// {
// path:'approve-event',
// children : [
// {
// path:':serialNumber/:caller',
// loadChildren: ()=> import('../pages/gabinete-digital/event-list/approve-event/approve-event.module').then(m => m.ApproveEventPageModule)
// },
// ]
// }
// ]
// },
// ],
{
path: 'agenda',
children: [
{
path:'',
loadChildren: () => AgendaPageModule
},
{
path:':eventId/:caller',
loadChildren: () => import('../pages/agenda/view-event/view-event.module').then( m => m.ViewEventPageModule),
},
{
path: 'eventId/:caller',
loadChildren: () => import('../pages/agenda/view-event/view-event.module').then( m => m.ViewEventPageModule),
},
{
path: 'edit-event',
loadChildren: () => import('../pages/agenda/edit-event/edit-event.module').then( m => m.EditEventPageModule)
},
{
path: 'emend-message-modal',
loadChildren: () => import('../pages/gabinete-digital/event-list/approve-event-modal/approve-event-modal.module').then( m => m.ApproveEventModalPageModule)
},
{
path: 'view-event',
loadChildren: () => import('../pages/agenda/view-event/view-event.module').then( m => m.ViewEventPageModule)
},
{
path:'event-list',
children: [
{
path:'',
loadChildren: ()=> import('../pages/gabinete-digital/event-list/event-list.module').then(m => m.EventListPageModule)
},
{
path:'approve-event',
children : [
{
path:':serialNumber/:caller',
loadChildren: ()=> import('../pages/gabinete-digital/event-list/approve-event/approve-event.module').then(m => m.ApproveEventPageModule)
},
]
}
]
},
],
// },
// {
// path: 'gabinete-digital',
// children: [
// {
// path:'',
// loadChildren: ()=> GabineteDigitalPageModule
// },
// {
// path:'expediente',
// children: [
// {
// path:'',
// loadChildren: ()=> import('../pages/gabinete-digital/expediente/expediente.module').then(m => m.ExpedientePageModule)
// },
// {
// path:':SerialNumber',
// loadChildren: ()=> import('../pages/gabinete-digital/expediente/expediente-detail/expediente-detail.module').then(m => m.ExpedienteDetailPageModule)
// },
// {
// path:':SerialNumber/:caller',
// loadChildren: ()=> import('../pages/gabinete-digital/expediente/expediente-detail/expediente-detail.module').then(m => m.ExpedienteDetailPageModule)
// },
// {
// path:'events/:eventId/:caller',
// loadChildren: ()=> import('../pages/events/event-detail/event-detail.module').then(m => m.EventDetailPageModule),
// },
// {
// path:'expediente-task-modal',
// loadChildren: ()=> import('../pages/gabinete-digital/expediente/expedient-task-modal/expedient-task-modal.module').then(m => m.ExpedientTaskModalPageModule),
// },
// ]
// },
// {
// path: 'expedientes-pr',
// children: [
// {
// path: '',
// loadChildren: ()=> import('../pages/gabinete-digital/expedientes-pr/expedientes-pr.module').then(m => m.ExpedientesPrPageModule),
// },
// {
// path:':SerialNumber/:caller',
// loadChildren: ()=> import('../pages/gabinete-digital/expedientes-pr/expediente-pr/expediente-pr.module').then(m => m.ExpedientePrPageModule),
// },
// ]
// },
// {
// path: 'despachos',
// children: [
// {
// path: '',
// loadChildren: ()=> import('../pages/gabinete-digital/despachos/despachos.module').then(m => m.DespachosPageModule),
// },
// {
// path:':SerialNumber/:caller',
// loadChildren: ()=> import('../pages/gabinete-digital/despachos/despacho/despacho.module').then(m => m.DespachoPageModule),
// },
// ]
// },
// {
// path: 'despachos-pr',
// children: [
// {
// path: '',
// loadChildren: ()=> import('../pages/gabinete-digital/despachos-pr/despachos-pr.module').then(m => m.DespachosPrPageModule),
// },
// {
// path:':SerialNumber/:caller',
// loadChildren: ()=> import('../pages/gabinete-digital/despachos-pr/despacho-pr/despacho-pr.module').then(m => m.DespachoPrPageModule),
// },
// ]
// },
// {
// path: 'pedidos',
// children: [
// {
// path: '',
// loadChildren: ()=> import('../pages/gabinete-digital/pedidos/pedidos.module').then(m => m.PedidosPageModule),
// },
// {
// path:':SerialNumber/:caller',
// loadChildren: ()=> import('../pages/gabinete-digital/pedidos/pedido/pedido.module').then(m => m.PedidoPageModule),
// },
// ]
// },
// {
// path: 'diplomas',
// children: [
// {
// path: '',
// loadChildren: ()=> import('../pages/gabinete-digital/diplomas/diplomas.module').then(m => m.DiplomasPageModule),
// },
// {
// path:':SerialNumber/:caller',
// loadChildren: ()=> import('../pages/gabinete-digital/diplomas/diploma/diploma.module').then(m => m.DiplomaPageModule),
// },
// ]
// },
// {
// path: 'diplomas-assinar',
// children: [
// {
// path: '',
// loadChildren: ()=> import('../pages/gabinete-digital/diplomas-assinar/diplomas-assinar.module').then(m => m.DiplomasAssinarPageModule),
// },
// {
// path:':SerialNumber/:caller',
// loadChildren: ()=> import('../pages/gabinete-digital/diplomas-assinar/diploma-assinar/diploma-assinar.module').then(m => m.DiplomaAssinarPageModule),
// },
// ]
// },
// {
// path: 'diplomas-gerar',
// children: [
// {
// path: '',
// loadChildren: ()=> import('../pages/gabinete-digital/diplomas-gerar/diplomas-gerar.module').then(m => m.DiplomasGerarPageModule),
// },
// {
// path:':SerialNumber/:caller',
// loadChildren: ()=> import('../pages/gabinete-digital/diplomas-gerar/diplomas-gerar/diplomas-gerar-routing.module').then(m => m.DiplomasGerarPageRoutingModule),
// },
// ]
// },
// {
// path:'event-list',
// children: [
// {
// path:'',
// loadChildren: ()=> import('../pages/gabinete-digital/event-list/event-list.module').then(m => m.EventListPageModule)
// },
// {
// path:'approve-event',
// children : [
// {
// path:':serialNumber/:caller',
// loadChildren: ()=> import('../pages/gabinete-digital/event-list/approve-event/approve-event.module').then(m => m.ApproveEventPageModule)
// },
// ]
// }
// ]
// },
// {
// path: 'events-to-approve',
// children: [
// {
// path: '',
// loadChildren: ()=> import('../shared/gabinete-digital/edit-event-to-approve/edit-event.module')
// }
// ]
// }
// ],
// },
},
{
path: 'gabinete-digital',
children: [
{
path:'',
loadChildren: ()=> GabineteDigitalPageModule
},
{
path:'expediente',
children: [
{
path:'',
loadChildren: ()=> import('../pages/gabinete-digital/expediente/expediente.module').then(m => m.ExpedientePageModule)
},
{
path:':SerialNumber',
loadChildren: ()=> import('../pages/gabinete-digital/expediente/expediente-detail/expediente-detail.module').then(m => m.ExpedienteDetailPageModule)
},
{
path:':SerialNumber/:caller',
loadChildren: ()=> import('../pages/gabinete-digital/expediente/expediente-detail/expediente-detail.module').then(m => m.ExpedienteDetailPageModule)
},
{
path:'events/:eventId/:caller',
loadChildren: ()=> import('../pages/events/event-detail/event-detail.module').then(m => m.EventDetailPageModule),
},
{
path:'expediente-task-modal',
loadChildren: ()=> import('../pages/gabinete-digital/expediente/expedient-task-modal/expedient-task-modal.module').then(m => m.ExpedientTaskModalPageModule),
},
]
},
{
path: 'expedientes-pr',
children: [
{
path: '',
loadChildren: ()=> import('../pages/gabinete-digital/expedientes-pr/expedientes-pr.module').then(m => m.ExpedientesPrPageModule),
},
{
path:':SerialNumber/:caller',
loadChildren: ()=> import('../pages/gabinete-digital/expedientes-pr/expediente-pr/expediente-pr.module').then(m => m.ExpedientePrPageModule),
},
]
},
{
path: 'despachos',
children: [
{
path: '',
loadChildren: ()=> import('../pages/gabinete-digital/despachos/despachos.module').then(m => m.DespachosPageModule),
},
{
path:':SerialNumber/:caller',
loadChildren: ()=> import('../pages/gabinete-digital/despachos/despacho/despacho.module').then(m => m.DespachoPageModule),
},
]
},
{
path: 'despachos-pr',
children: [
{
path: '',
loadChildren: ()=> import('../pages/gabinete-digital/despachos-pr/despachos-pr.module').then(m => m.DespachosPrPageModule),
},
{
path:':SerialNumber/:caller',
loadChildren: ()=> import('../pages/gabinete-digital/despachos-pr/despacho-pr/despacho-pr.module').then(m => m.DespachoPrPageModule),
},
]
},
{
path: 'pedidos',
children: [
{
path: '',
loadChildren: ()=> import('../pages/gabinete-digital/pedidos/pedidos.module').then(m => m.PedidosPageModule),
},
{
path:':SerialNumber/:caller',
loadChildren: ()=> import('../pages/gabinete-digital/pedidos/pedido/pedido.module').then(m => m.PedidoPageModule),
},
]
},
{
path: 'diplomas',
children: [
{
path: '',
loadChildren: ()=> import('../pages/gabinete-digital/diplomas/diplomas.module').then(m => m.DiplomasPageModule),
},
{
path:':SerialNumber/:caller',
loadChildren: ()=> import('../pages/gabinete-digital/diplomas/diploma/diploma.module').then(m => m.DiplomaPageModule),
},
]
},
{
path: 'diplomas-assinar',
children: [
{
path: '',
loadChildren: ()=> import('../pages/gabinete-digital/diplomas-assinar/diplomas-assinar.module').then(m => m.DiplomasAssinarPageModule),
},
{
path:':SerialNumber/:caller',
loadChildren: ()=> import('../pages/gabinete-digital/diplomas-assinar/diploma-assinar/diploma-assinar.module').then(m => m.DiplomaAssinarPageModule),
},
]
},
{
path: 'diplomas-gerar',
children: [
{
path: '',
loadChildren: ()=> import('../pages/gabinete-digital/diplomas-gerar/diplomas-gerar.module').then(m => m.DiplomasGerarPageModule),
},
{
path:':SerialNumber/:caller',
loadChildren: ()=> import('../pages/gabinete-digital/diplomas-gerar/diplomas-gerar/diplomas-gerar-routing.module').then(m => m.DiplomasGerarPageRoutingModule),
},
]
},
{
path:'event-list',
children: [
{
path:'',
loadChildren: ()=> import('../pages/gabinete-digital/event-list/event-list.module').then(m => m.EventListPageModule)
},
{
path:'approve-event',
children : [
{
path:':serialNumber/:caller',
loadChildren: ()=> import('../pages/gabinete-digital/event-list/approve-event/approve-event.module').then(m => m.ApproveEventPageModule)
},
]
}
]
},
{
path: 'events-to-approve',
children: [
{
path: '',
loadChildren: ()=> import('../shared/gabinete-digital/edit-event-to-approve/edit-event.module')
}
]
}
],
},
{
path: 'search',
children: [
+4 -1
View File
@@ -43,8 +43,11 @@ export class TokenInterceptor implements HttpInterceptor {
return next.handle(request).pipe(
catchError((error) => {
console.log('interceptor ',error)
if (error instanceof HttpErrorResponse && error.status === 401) {
return this.handle401Error(request, next);
} else if (error.url.includes('https://gdapi-dev.dyndns.info/stage/api/v2') && error.status === 0){
return this.handle401Error(request, next);
} else {
return throwError(error);
}
@@ -125,7 +128,7 @@ export class TokenInterceptor implements HttpInterceptor {
if(pathBeforeGoOut != "/auth") {
this.httpErrorHandle.httpsSucessMessagge('sessonExpired')
}
})
}
return of(false);
+1 -3
View File
@@ -43,7 +43,7 @@ export class ChatModule {
this.typingCallback[id].pipe(
switchMap(() => timer(2000)),
).subscribe(() => {
console.log('111111==============')
// console.log('111111==============')
// this.memoryDataSource.dispatch(removeUserTyping({data: {...e} as any}))
this.localDataSource.removeUserTyping(e)
})
@@ -51,8 +51,6 @@ export class ChatModule {
this.typingCallback[id].next()
}
} else {
console.log('e--', e)
}
})
}
@@ -1,6 +1,7 @@
import { MessageTable } from "src/app/module/chat/infra/database/dexie/schema/message";
import { RoomListItemOutPutDTO, RoomListOutPutDTO } from "../../../dto/room/roomListOutputDTO";
export function messageListDetermineChanges(serverList: any[], localList: any[]) {
export function messageListDetermineChanges(serverList: MessageTable[], localList: MessageTable[]) {
localList = localList.filter(e => e.id)
// Convert lists to dictionaries for easier comparison
@@ -34,7 +34,11 @@ export const MessageOutPutDataDTOSchema = z.object({
reaction: z.string(),
sender: z.object({}),
}).array(),
info: z.array(z.object({})),
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),
@@ -8,38 +8,41 @@ import { chatDatabase } from '../../infra/database/dexie/service';
import { ok } from 'neverthrow';
import { err, Result } from 'neverthrow';
import { MemberListUPdateStatusInputDTO } from '../../domain/use-case/socket/member-list-update-status-use-case.service';
import { MemberTable } from '../../infra/database/dexie/schema/members';
import { MemberTable, MemberTableSchema } from '../../infra/database/dexie/schema/members';
import { from } from 'rxjs';
@Injectable({
providedIn: 'root'
})
export class MemberListLocalRepository extends DexieRepository<RoomTable> {
export class MemberListLocalRepository extends DexieRepository<MemberTable> {
constructor() {
super(chatDatabase.room, RoomTableSchema)
super(chatDatabase.members, MemberTableSchema)
}
// messageDataSource.message.hook('creating', (primKey, obj, trans) => {
// // const newMessage = await trans.table('message').get(primKey);
// this.messageSubject.next(obj);
// // return newMessage
// })
async directMember({roomId, currentUserId}) {
try {
let a = await chatDatabase.members.where('roomId')
.equals(roomId)
.and(message => message.wxUserId !== currentUserId)
.first()
return ok(a)
} catch (e) {
return err(e)
}
}
async addMember(data: MemberTable) {
try {
data.$roomIdUserId = data.roomId + data.wxUserId
const result = await chatDatabase.members.add(data)
return ok(result)
} catch (e) {
return err(false)
}
data.$roomIdUserId = data.roomId + data.wxUserId
return this.insert(data)
}
async updateMemberRole(data: MemberTable) {
try {
const result = await chatDatabase.members.where({
wxUserId:data.wxUserId,
wxUserId: data.wxUserId,
roomId: data.roomId,
}).modify(data);
@@ -100,19 +103,11 @@ export class MemberListLocalRepository extends DexieRepository<RoomTable> {
return liveQuery(() => chatDatabase.members.get($roomIdUserId)) as any;
}
getItemsLive(): Observable<RoomListOutPutDTO[]> {
return liveQuery(() => chatDatabase.room.toArray()) as any;
}
getRoomByIdLive(id: any): Observable<RoomListItemOutPutDTO | undefined> {
return liveQuery(() => chatDatabase.room.get(id)) as any;
}
async getRoomMemberById(roomId: any) {
return await chatDatabase.members.where({roomId}).toArray()
}
getRoomMemberByIdLive(roomId: any) {
return liveQuery(() => chatDatabase.members.where({roomId}).toArray())
return from (liveQuery(() => chatDatabase.members.where({roomId}).toArray()))
}
getRoomMemberNoneAdminByIdLive(roomId: any) {
@@ -19,6 +19,22 @@ interface msgObj {
requestId: string;
}
interface sendDeliverAt {
memberId: number,
messageId:string,
roomId: string,
requestId: string
}
export interface sendReadAt {
memberId: number,
messageId:string,
roomId: string,
requestId: string
}
@Injectable({
providedIn: 'root'
})
@@ -48,6 +64,24 @@ export class MessageSocketRepositoryService {
return result;
}
async sendDeliverAt(data: sendDeliverAt) {
const result = await this.socket.sendData<any>({
method: 'DeliverAt',
data: data as any,
})
return result;
}
async sendReadAt(data: sendReadAt) {
const result = await this.socket.sendData<any>({
method: 'ReadAt',
data: data as any,
})
return result;
}
listenToMessages() {
return this.socket.getMessage()
}
@@ -62,7 +96,7 @@ export class MessageSocketRepositoryService {
return this.socket.getMessageUpdate()
}
reactToMessageSocket(data) {
this.socket.sendData({
method: 'ReactMessage',
@@ -94,6 +128,6 @@ export class MessageSocketRepositoryService {
}
}
@@ -7,6 +7,7 @@ import { ValidateSchema } from 'src/app/services/decorators/validate-schema.deco
import { chatDatabase } from '../../infra/database/dexie/service';
import { RoomTable, RoomTableSchema } from '../../infra/database/dexie/schema/room';
import { DexieRepository } from 'src/app/infra/repository/dexie/dexie-repository.service';
import { from } from 'rxjs';
@Injectable({
providedIn: 'root'
@@ -81,7 +82,7 @@ export class RoomLocalRepository extends DexieRepository<RoomTable> {
getItemsLive(){
return liveQuery(() => chatDatabase.room.toArray());
return from (liveQuery(() => chatDatabase.room.toArray()));
}
getRoomByIdLive(id: any) {
@@ -25,6 +25,7 @@ import { SendLocalMessagesUseCaseService } from './use-case/messages-send-offlin
import { RemoveMemberUseCaseService } from './use-case/member/-use-case.service'
import { AddMemberUseCaseService } from './use-case/member-add-use-case.service'
import { UpdateRoomByIdUseCaseService } from './use-case/room-update-by-id-use-case.service'
import { MessageMarkAsReadUseCaseService } from './use-case/message/message-mark-as-read-use-case.service'
import { GetMessageAttachmentLocallyUseCaseService } from 'src/app/module/chat/domain/use-case/message-get-attachment-localy-use-case.service';
import { GetRoomListUseCaseService } from 'src/app/module/chat/domain/use-case/room-get-list-use-case.service';
import { filter } from 'rxjs/operators';
@@ -36,6 +37,7 @@ import { UserRemoveListInputDTO } from '../data/dto/room/userRemoveListInputDTO'
import { AddMemberToRoomInputDTO } from '../data/dto/room/addMemberToRoomInputDto';
import { RoomUpdateInputDTO } from '../data/dto/room/roomUpdateInputDTO';
import { RoomType } from "src/app/module/chat/domain/entity/group";
import { sendReadAt } from "src/app/module/chat/data/repository/message/message-live-signalr-data-source.service";
export const InstanceId = uuidv4();
@@ -73,7 +75,8 @@ export class ChatServiceService {
private UpdateRoomByIdUseCaseService: UpdateRoomByIdUseCaseService,
private RemoveMemberUseCaseService: RemoveMemberUseCaseService,
private MessageReadAtByIdUseCaseService: MessageReadAtByIdUseCaseService,
private SendLocalMessagesUseCaseService: SendLocalMessagesUseCaseService
private SendLocalMessagesUseCaseService: SendLocalMessagesUseCaseService,
private MessageMarkAsReadUseCaseService: MessageMarkAsReadUseCaseService
) {
this.messageLiveSignalRDataSourceService.getMessageDelete()
.pipe()
@@ -102,7 +105,6 @@ export class ChatServiceService {
})
).subscribe(async (message) => {
if(message?.id) {
console.log('create message')
this.SocketMessageCreateUseCaseService.execute(message)
}
})
@@ -115,7 +117,6 @@ export class ChatServiceService {
}
})
).subscribe(async (message) => {
console.log('123', message)
this.MemberListUpdateStatusUseCaseService.execute(message.data as any)
})
@@ -213,6 +214,10 @@ export class ChatServiceService {
}
sendReadAt(sendReadAt: sendReadAt) {
return this.MessageMarkAsReadUseCaseService.execute(sendReadAt)
}
listenToIncomingMessage(roomId:string) {
return this.ListenMessageByRoomIdNewUseCase.execute({roomId})
}
@@ -14,12 +14,18 @@ export const MessageEntitySchema = z.object({
oneShot: z.boolean(),
sentAt: z.string().optional(),
requireUnlock: z.boolean(),
editedAt: z.string().nullable().optional(),
sender: z.object({
wxUserId: z.number(),
wxFullName: z.string(),
wxeMail: z.string(),
userPhoto: z.string(),
}),
info: z.array(z.object({
memberId: z.number(),
readAt: z.string().nullable(),
deliverAt: z.string().nullable()
})).optional(),
sending: z.boolean().optional(),
attachments: z.array(z.object({
fileType: z.nativeEnum(MessageAttachmentFileType),
@@ -47,6 +53,11 @@ export class MessageEntity implements Message {
oneShot: boolean = false
sentAt: string
requireUnlock: boolean = false
info: {
memberId?: number
readAt?: string,
deliverAt?: string
}[] = []
sender: {
wxUserId: number,
wxFullName: string,
@@ -0,0 +1,9 @@
import { Injectable } from '@angular/core';
@Injectable({
providedIn: 'root'
})
export class RoomExpirationService {
constructor() { }
}
@@ -14,8 +14,12 @@ export class ListenSendMessageUseCase {
execute({roomId}: {roomId: string}) {
console.log('reciee message')
return this.MessageSocketRepositoryService.listenToMessages().pipe(
filter((message) => message?.requestId?.startsWith(InstanceId) && message?.roomId == roomId),
filter((message) => {
console.log(message, roomId)
return message?.requestId?.startsWith(InstanceId) && message?.roomId == roomId
}),
map(message => message)
)
@@ -15,8 +15,9 @@ import { MessageTable } from '../../infra/database/dexie/schema/message';
import { MessageMapper } from '../mapper/messageMapper';
import { SignalRService } from '../../infra/socket/signal-r.service';
import { RoomType } from "src/app/module/chat/domain/entity/group";
import { TracingType, XTracerAsync } from 'src/app/services/monitoring/opentelemetry/tracer';
import { MemberListLocalRepository } from 'src/app/module/chat/data/repository/member-list-local-repository.service'
import { SessionStore } from 'src/app/store/session.service';
const MessageInputUseCaseSchema = z.object({
memberId: z.number(),
roomId: z.string(),
@@ -39,11 +40,13 @@ export class MessageCreateUseCaseService {
private AttachmentLocalRepositoryService: AttachmentLocalDataSource,
private messageLocalDataSourceService: MessageLocalDataSourceService,
private messageLiveSignalRDataSourceService: SignalRService,
private messageSocketRepositoryService: MessageSocketRepositoryService
private messageSocketRepositoryService: MessageSocketRepositoryService,
private MemberListLocalRepository: MemberListLocalRepository
) { }
async execute(message: MessageEntity, messageEnum: RoomType) {
@XTracerAsync({name:'MessageCreateUseCaseService', module:'chat', bugPrint: true})
async execute(message: MessageEntity, messageEnum: RoomType, tracing?: TracingType) {
const validation = zodSafeValidation<MessageEntity>(MessageEntitySchema, message)
@@ -93,15 +96,32 @@ export class MessageCreateUseCaseService {
//====================
message.sending = true
const DTO = MessageMapper.fromDomain(message, message.requestId)
let sendMessageResult: Result<MessageOutPutDataDTO, any>
if(messageEnum == RoomType.Group) {
const DTO = MessageMapper.fromDomain(message, message.requestId)
sendMessageResult = await this.messageLiveSignalRDataSourceService.sendMessage<MessageOutPutDataDTO>(DTO)
} else {
sendMessageResult = await this.messageSocketRepositoryService.sendDirectMessage(DTO)
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.sendDirectMessage(DTO)
} else {
console.log('not found direct users', getRoomMembers.error)
}
}
}
// return this sendMessageResult
if(sendMessageResult.isOk()) {
@@ -127,7 +147,7 @@ export class MessageCreateUseCaseService {
await this.messageLocalDataSourceService.update(message.$id, {sending: false, $id: message.$id})
return err('no connection')
}
}
} else {
@@ -136,6 +156,11 @@ export class MessageCreateUseCaseService {
zodErrorList: validation.error.errors,
data: message.attachments
})
} else {
Logger.error('failed to send message, validation failed', {
zodErrorList: validation.error.errors,
data: message
})
}
}
@@ -3,8 +3,7 @@ import { z } from 'zod';
import { SafeValidateSchema, ValidateSchema } from 'src/app/services/decorators/validate-schema.decorator';
import { MessageRemoteDataSourceService } from '../../data/repository/message/message-remote-data-source.service';
import { MessageSocketRepositoryService } from '../../data/repository/message/message-live-signalr-data-source.service';
import { XTracerAsync, TracingType } from 'src/app/services/monitoring/opentelemetry/tracer';
export const MessageDeleteInputDTOSchema = z.object({
requestId: z.string().optional(),
roomId: z.string(),
@@ -22,8 +21,11 @@ export class MessageDeleteLiveUseCaseService {
public repository: MessageSocketRepositoryService
) { }
@SafeValidateSchema(MessageDeleteInputDTOSchema, 'MessageDeleteUseCaseService')
async execute(data: MessageDeleteInputDTO) {
@XTracerAsync({name:'MessageDeleteLiveUseCaseService', module:'chat', bugPrint: true})
async execute(data: MessageDeleteInputDTO, tracing?: TracingType) {
tracing.log('MessageDeleteLiveUseCaseService payload', {
data: data
})
return this.repository.sendMessageDelete(data)
}
}
@@ -0,0 +1,18 @@
import { Injectable } from '@angular/core';
import { MessageSocketRepositoryService, sendReadAt } from '../../../data/repository/message/message-live-signalr-data-source.service';
import { XTracerAsync, TracingType } from 'src/app/services/monitoring/opentelemetry/tracer';
@Injectable({
providedIn: 'root'
})
export class MessageMarkAsReadUseCaseService {
constructor(
private MessageSocketRepositoryService: MessageSocketRepositoryService,
) { }
@XTracerAsync({name:'MessageMarkAsReadUseCaseService', module:'chat', bugPrint: true})
async execute(sendReadAt: sendReadAt, tracing?: TracingType) {
return this.MessageSocketRepositoryService.sendReadAt(sendReadAt)
}
}
@@ -4,7 +4,7 @@ import { RoomRemoteDataSourceService } from '../../data/repository/room-remote-r
import { RoomLocalRepository } from '../../data/repository/room-local-repository.service';
import { captureAndReraiseAsync } from 'src/app/services/decorators/captureAndReraiseAsync';
import { isHttpResponse } from 'src/app/services/http.service';
import { CronJobService } from 'src/app/utils/task-scheduler'
@Injectable({
providedIn: 'root'
})
@@ -14,6 +14,7 @@ export class GetRoomListUseCaseService {
private roomRemoteDataSourceService: RoomRemoteDataSourceService,
// private roomMemoryDataSourceService: Store<RoomRemoteDataSourceState>,
private roomLocalDataSourceService: RoomLocalRepository,
private CronJobService: CronJobService
) { }
@captureAndReraiseAsync('RoomRepositoryService/list')
@@ -28,6 +29,12 @@ export class GetRoomListUseCaseService {
for( const roomData of roomsToInsert) {
this.roomLocalDataSourceService.createRoom(roomData.chatRoom)
if(roomData.chatRoom.expirationDate) {
console.log('room expiration date schedule')
this.CronJobService.createCronJob('remove expired room', new Date(roomData.chatRoom.expirationDate), this.execute)
}
}
for( const roomData of roomsToUpdate) {
@@ -13,7 +13,7 @@ export class SocketMessageCreateUseCaseService {
private messageLocalDataSourceService: MessageLocalDataSourceService,
) { }
@XTracerAsync({name:'Socket-Message-Create-UseCase', bugPrint: true})
@XTracerAsync({name:'Socket-Message-Create-UseCase', module:'chat', bugPrint: true})
async execute(input: any, tracing?: TracingType) {
ParamsValidation(MessageOutPutDataDTOSchema, input, tracing)
@@ -15,7 +15,7 @@ export class SocketMessageUpdateUseCaseService {
) { }
@XTracerAsync({name:'Socket-Message-Update-UseCase', bugPrint: true})
@XTracerAsync({name:'Socket-Message-Update-UseCase', bugPrint: true, module:'chat',})
async execute(data: MessageOutPutDataDTO, tracing?: TracingType) {
ParamsValidation(MessageOutPutDataDTOSchema, data, tracing)
@@ -30,11 +30,13 @@ export class SocketMessageUpdateUseCaseService {
if(result.isOk()) {
tracing?.addEvent("Message found")
return await this.messageLocalDataSourceService.update(result.value.$id, incomingMessage)
const updateResult = await this.messageLocalDataSourceService.update(result.value.$id, incomingMessage)
tracing.setAttribute('outcome', 'success')
return updateResult
} else {
tracing?.addEvent("Message not found")
}
tracing.setAttribute('outcome', 'success')
}
}
@@ -3,8 +3,11 @@ import { MessageLocalDataSourceService } from '../../data/repository/message/mes
import { messageListDetermineChanges } from '../../data/async/list/rooms/messageListChangedetector';
import { MessageTable } from '../../infra/database/dexie/schema/message';
import { MessageRemoteDataSourceService } from '../../data/repository/message/message-remote-data-source.service';
import { MessageSocketRepositoryService } from '../../data/repository/message/message-live-signalr-data-source.service';
import { ok } from 'neverthrow';
import { RoomLocalRepository } from '../../data/repository/room-local-repository.service';
import { SessionStore } from 'src/app/store/session.service';
import { Logger } from 'src/app/services/logger/main/service';
@Injectable({
providedIn: 'root'
@@ -14,6 +17,7 @@ export class SyncAllRoomMessagesService {
constructor(
private messageLocalDataSourceService: MessageLocalDataSourceService,
private messageRemoteDataSourceService: MessageRemoteDataSourceService,
private MessageSocketRepositoryService: MessageSocketRepositoryService,
private roomLocalDataSourceService: RoomLocalRepository,
) { }
@@ -35,15 +39,38 @@ export class SyncAllRoomMessagesService {
const { addedItems, changedItems, deletedItems } = messageListDetermineChanges(result.value.data, localResult)
for(const message of changedItems) {
let clone: MessageTable = message
clone.roomId = room.id
this.messageLocalDataSourceService.findOrUpdate(clone)
const me = message.info.find(e => e.memberId == SessionStore.user.UserId)
if(!me) {
this.MessageSocketRepositoryService.sendDeliverAt({
memberId: SessionStore.user.UserId,
messageId: message.id,
roomId: message.roomId,
requestId: "string"
})
}
}
for(const message of addedItems) {
let clone: MessageTable = message
clone.roomId = room.id
const me = message.info.find(e => e.memberId == SessionStore.user.UserId)
if(!me) {
this.MessageSocketRepositoryService.sendDeliverAt({
memberId: SessionStore.user.UserId,
messageId: message.id,
roomId: message.roomId,
requestId: "string"
})
}
}
@@ -54,6 +81,8 @@ export class SyncAllRoomMessagesService {
this.messageLocalDataSourceService.deleteByMessageId(message.id)
}
} else {
Logger.error('failed to get room message '+room.id)
}
}
@@ -16,7 +16,7 @@ export const AttachmentTableSchema = z.object({
docId: z.string().optional(),
mimeType: z.string().optional(),
id: z.string().optional(),
description: z.string().optional(),
description: z.string().optional()
})
export type AttachmentTable = z.infer<typeof AttachmentTableSchema>
@@ -11,6 +11,7 @@ export const MessageTableSchema = z.object({
canEdit: z.boolean(),
oneShot: z.boolean(),
sentAt: z.string().optional(),
editedAt: z.string().nullable().optional(),
requireUnlock: z.boolean(),
sender: z.object({
wxUserId: z.number(),
@@ -26,7 +27,11 @@ export const MessageTableSchema = z.object({
reaction: z.string(),
sender: z.object({}),
}).array().optional(),
info: z.array(z.object({})).optional(),
info: z.array(z.object({
memberId: z.number(),
readAt: z.string().nullable(),
deliverAt: z.string().nullable()
})).optional(),
attachments: z.array(z.object({
fileType: z.nativeEnum(MessageAttachmentFileType),
source: z.nativeEnum(MessageAttachmentSource),
+1 -3
View File
@@ -157,7 +157,7 @@ export class SignalRConnection {
const requestId = uuidv4()
if(this.connectionStateSubject.value == true) {
console.log('send typing', data)
// console.log('send typing', data)
try {
this.hubConnection.invoke("Typing", data)
@@ -276,7 +276,6 @@ export class SignalRConnection {
});
this.hubConnection.on('TypingMessage', (_typing: UserTypingDTO) => {
console.log('Typing', _typing)
this.typingSubject.next(_typing);
this.sendDataSubject.next({
method: 'ReceiveMessage',
@@ -285,7 +284,6 @@ export class SignalRConnection {
});
this.hubConnection.on('AvailableUsers', (data: any) => {
console.log('AvailableUsers', data)
this.typingSubject.next(data);
this.sendDataSubject.next({
method: 'AvailableUsers',
+103
View File
@@ -1,5 +1,11 @@
// import { DateUtils } from './date';
import { environment } from "src/environments/environment";
import { openTelemetryLogging } from "../../monitoring/opentelemetry/logging";
import { SessionStore } from "src/app/store/session.service";
import { DeviceInfo } from "@capacitor/device";
import { Device } from '@capacitor/device';
export type MessageType = {
message: string;
context?: string;
@@ -15,6 +21,12 @@ function getCurrentTime() {
return `${hours}:${minutes}:${seconds}.${milliseconds}`;
}
let device: DeviceInfo;
Device.getInfo().then(e => {
device = e
});
export class Logger {
constructor() {}
@@ -25,6 +37,25 @@ export class Logger {
'color: #00897B', // CSS Style
Object.assign(obj, { createdAt: getCurrentTime(), message })
);
if(environment.apiURL != 'https://gdqas-api.oapr.gov.ao/api/') {
openTelemetryLogging.send({
type: 'graylog',
spanContext: null,
payload: {
message: message,
object: {
...obj,
spanId: null,
name,
user: SessionStore?.user?.FullName,
device_name: device?.name || device?.model,
commit_date: environment.version.lastCommitTime,
}
}
})
}
}
static debug(message: string, obj = {}): void {
@@ -33,6 +64,24 @@ export class Logger {
'color: #039BE5', // CSS Style
Object.assign(obj, {createdAt: getCurrentTime(), message })
);
if(environment.apiURL != 'https://gdqas-api.oapr.gov.ao/api/') {
openTelemetryLogging.send({
type: 'graylog',
spanContext: null,
payload: {
message: message,
object: {
...obj,
spanId: null,
name,
user: SessionStore?.user?.FullName,
device_name: device?.name || device?.model,
commit_date: environment.version.lastCommitTime,
}
}
})
}
}
static info(message: string, obj = {}): void {
@@ -44,6 +93,24 @@ export class Logger {
obj,
'\n',
);
if(environment.apiURL != 'https://gdqas-api.oapr.gov.ao/api/') {
openTelemetryLogging.send({
type: 'graylog',
spanContext: null,
payload: {
message: message,
object: {
...obj,
spanId: null,
name,
user: SessionStore?.user?.FullName,
device_name: device?.name || device?.model,
commit_date: environment.version.lastCommitTime,
}
}
})
}
}
static warn(message: string, obj = {}): void {
@@ -55,6 +122,24 @@ export class Logger {
obj,
'\n',
);
if(environment.apiURL != 'https://gdqas-api.oapr.gov.ao/api/') {
openTelemetryLogging.send({
type: 'graylog',
spanContext: null,
payload: {
message: message,
object: {
...obj,
spanId: null,
name,
user: SessionStore?.user?.FullName,
device_name: device?.name || device?.model,
commit_date: environment.version.lastCommitTime,
}
}
})
}
}
static error(message?: string, obj = {}): void {
@@ -67,6 +152,24 @@ export class Logger {
obj,
'\n',
);
if(environment.apiURL != 'https://gdqas-api.oapr.gov.ao/api/') {
openTelemetryLogging.send({
type: 'graylog',
spanContext: null,
payload: {
message: message,
object: {
...obj,
spanId: null,
name,
user: SessionStore?.user?.FullName,
device_name: device?.name || device?.model,
commit_date: environment.version.lastCommitTime,
}
}
})
}
}
fatal(error?: any, message?: string, context?: string): void {}
@@ -1,7 +1,7 @@
import { v4 as uuidv4 } from 'uuid';
import { SemanticAttributes } from '@opentelemetry/semantic-conventions';
import { Tracer, Span } from '@opentelemetry/sdk-trace-base';
import { OpentelemetryAgendaProvider, OpentelemetryInterceptorProvider, OpentelemetryLogging, OpentelemetryNotificationProvider } from './opentelemetry';
import { OpentelemetryAgendaProvider, OpentelemetryChatProvider, OpentelemetryInterceptorProvider, OpentelemetryLogging, OpentelemetryNotificationProvider } from './opentelemetry';
import { Device, DeviceInfo } from '@capacitor/device';
import { SessionStore } from 'src/app/store/session.service';
import { environment } from 'src/environments/environment';
@@ -12,7 +12,7 @@ import {
} from '@opentelemetry/api';
const tracerInstance = OpentelemetryAgendaProvider.getTracer('example-tracer-hole', '111', {})
const tracerNotificationInstance = OpentelemetryNotificationProvider.getTracer('example-tracer-hole', '111', {})
const tracerChat = OpentelemetryChatProvider.getTracer('OpentelemetryChatProvider','some' ,{})
let device: DeviceInfo;
Device.getInfo().then(e => {
@@ -45,6 +45,8 @@ const createTracingInstance = ({bugPrint, name, module, autoFinish}): TracingTyp
if(module == 'notification') {
_tracerInstance = tracerNotificationInstance
} else if (module == 'chat') {
_tracerInstance = tracerChat
} else {
_tracerInstance = tracerInstance
}
@@ -184,6 +186,9 @@ export function XTracerAsync({ name, bugPrint, module = null, autoFinish = true,
return result
} catch (e) {
tracing.setAttribute('catch', 'true')
tracing.log("cath", {
error: e
})
if(autoFinish) {
setTimeout(tracing.finish , daley)
@@ -226,6 +231,9 @@ export function XTracer({ name, bugPrint, module, autoFinish = true, daley =0 })
} catch (e) {
tracing.setAttribute('catch', 'true')
tracing.log("cath", {
error: e
})
if(autoFinish) {
setTimeout(tracing.finish , daley)
+6
View File
@@ -67,6 +67,9 @@
</div>
<!-- <div class="item-date font-13-em" [class.item-date-active]="room.id == idSelected">{{room.duration}}</div> -->
</div>
<div class="item-date font-13-em" [class.item-date-active]="room.id == idSelected">
{{ expirationDate[room.id] !== null ? expirationDate[room.id] + ' seconds left' : 'No expiration' }}
</div>
<!-- <div *ngIf="room.lastMessage" class="item-description d-flex align-items-center" [class.item-description-active]="room.id ==idSelected">
<div class="item-message font-13-em add-ellipsis white-space-nowrap" *ngIf="room.otherUserType == false"> {{room.lastMessage.msg}} </div>
<div class="font-13-em" *ngIf="room.otherUserType == true">está escrever ...</div>
@@ -101,6 +104,9 @@
</div>
<!-- <div class="item-date font-13-em" [class.item-date-active]="room.id == idSelected">{{room.duration}}</div> -->
</div>
<div class="item-date font-13-em" [class.item-date-active]="room.id == idSelected">
{{ expirationDate[room.id] !== null ? expirationDate[room.id] + ' seconds left' : 'No expiration' }}
</div>
<!-- <div *ngIf="room.lastMessage" class="item-description d-flex align-items-center" [class.item-description-active]="room.id ==idSelected">
<div class="item-message font-13-em add-ellipsis white-space-nowrap" *ngIf="room.otherUserType == false"> {{room.lastMessage.msg}} </div>
<div class="font-13-em" *ngIf="room.otherUserType == true">está escrever ...</div>
+40 -2
View File
@@ -21,6 +21,8 @@ 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-local-repository.service'
import { RoomTable } from 'src/app/module/chat/infra/database/dexie/schema/room';
import { map, tap } from 'rxjs/operators';
import { interval, Subscription } from 'rxjs';
@Component({
selector: 'app-chat',
@@ -53,7 +55,12 @@ export class ChatPage implements OnInit {
routerSubscription
// count$: Observable<RoomRemoteDataSourceState>;
items$!: DexieObservable<RoomTable[]>;
//items$!: DexieObservable<RoomTable[]>;
items$!: Observable<RoomTable[]>;
private rooms: RoomTable[] = [];
private subscription: Subscription;
expirationDate = {}
private intervalSubscription: Subscription;
constructor(
private modalController: ModalController,
@@ -82,7 +89,28 @@ export class ChatPage implements OnInit {
ngOnInit() {
this.items$ = this.roomLocalDataSourceService.getItemsLive()
this.items$ = this.roomLocalDataSourceService.getItemsLive().pipe(
map(rooms => rooms.map(room => ({
...room,
minutesLeft: this.getSecondsLeft(room.expirationDate)
}))),
tap((roomList) => {
this.rooms = roomList
})
);
// Create the interval observable
const interval$ = interval(1000).pipe(
tap(() => {
for (const room of this.rooms) {
this.expirationDate[room.id] = this.getSecondsLeft(room.expirationDate);
}
})
);
// Subscribe to the interval observable
this.intervalSubscription = interval$.subscribe();
this.ChatServiceService.getRoomList()
this.hideRefreshButton();
@@ -103,10 +131,20 @@ export class ChatPage implements OnInit {
}
getSecondsLeft(expirationDate: any): number | null {
if (!expirationDate) return null;
const now = new Date().getTime();
const expTime = new Date(expirationDate).getTime();
return Math.max(Math.floor((expTime - now) / 1000), 0);
}
ngOnDestroy() {
// this.setStatus('offline');
this.routerSubscription?.unsubscribe();
// Unsubscribe from the interval to prevent memory leaks
if (this.intervalSubscription) {
this.intervalSubscription.unsubscribe();
}
}
@@ -3,7 +3,7 @@
<div class="main-header">
<div class="title-content">
<div class="back-icon cursor-pointer">
<button class="btn-no-color" (click)="close()">
<button class="btn-no-color" (click)="close(roomId)">
<ion-icon *ngIf="ThemeService.currentTheme == 'default' " slot="end" src='assets/images/icons-arrow-arrow-left.svg'></ion-icon>
<ion-icon *ngIf="ThemeService.currentTheme == 'gov' " slot="end" src='assets/images/theme/gov/icons-calendar-arrow-left.svg'></ion-icon>
</button>
@@ -1,4 +1,4 @@
import { Component, EventEmitter, OnInit, Output } from '@angular/core';
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
import { ModalController } from '@ionic/angular';
import { GroupMessagesPage } from '../group-messages/group-messages.page';
import { ThemeService } from 'src/app/services/theme.service'
@@ -32,7 +32,7 @@ export class ContactsPage implements OnInit {
@Output() backToChat: EventEmitter<any> = new EventEmitter<any>();
@Output() closeAllDesktopComponents: EventEmitter<any> = new EventEmitter<any>();
roomId= ''
@Input() roomId: string;
constructor(
private modalController: ModalController,
@@ -122,14 +122,14 @@ export class ContactsPage implements OnInit {
onChange(event) {
}
}
clicked() {}
selectOnce = true
async select(user: UserContacts) {
const message = new MessageEntity();
message.sender = {
@@ -142,8 +142,10 @@ export class ContactsPage implements OnInit {
message.receiverId = user.wxUserId
message.message = 'hello'
const result = await this.chatServiceService.sendMessage(message, RoomType.Group)
const result = await this.chatServiceService.sendMessage(message, RoomType.Direct)
console.log('result', result);
if(result.isOk()) {
this.close(result.value.roomId)
} else {
@@ -48,7 +48,7 @@
</div>
</ion-list>
</div>
pppp
<div *ngFor="let userContainer of userContainer | keyvalue;" >
<div class="item-divider">
@@ -29,7 +29,6 @@
</ion-refresher>
<div class="main-content">
------
<ion-progress-bar class="position-absolute" type="indeterminate" *ngIf="loading"></ion-progress-bar>
<ion-virtual-scroll [items]="userList" approxItemHeight="70px" [headerFn]="separateLetter">
@@ -67,11 +67,12 @@
</div>
<div *ngIf="attachment.fileType == MessageAttachmentFileType.Image">
<img
[src]="attachment.safeFile"
(load)="onImageLoad(message, messageIndex)"
(error)="onImageError()">
</div>
<img
*ngIf="attachment.oneShot != true"
[src]="attachment.safeFile"
(load)="onImageLoad(message, messageIndex)"
(error)="onImageError()">
</div>
<div *ngIf="attachment.fileType == MessageAttachmentFileType.Audio">
<audio [src]="attachment.safeFile|safehtml" preload="metadata" class="flex-grow-1" controls controlsList="nodownload noplaybackrate"></audio>
@@ -92,7 +93,6 @@
<ion-label>{{ attachment.fileName}}</ion-label>
</div>
</div>
</div>
@@ -112,8 +112,19 @@
</div>
<div style="text-align: end;">
<div *ngIf="message.messageStatus != 'send'" style="font-size: .6875rem;">A enviar</div>
<div *ngIf="message.messageStatus == 'send'" style="font-size: .6875rem;">Enviado</div>
<!-- <div *ngIf="messageStatus(message) == 'enviar'" style="font-size: .6875rem;">A enviar</div>
<div *ngIf="messageStatus(message) == 'enviado'" style="font-size: .6875rem;">Enviado</div>
<div *ngIf="messageStatus(message) == 'allReceived'" style="font-size: .6875rem;">allReceived</div>
<div *ngIf="messageStatus(message) == 'allViewed'" style="font-size: .6875rem;">allViewed</div>
-->
<div *ngIf="totalMembers != 0">
<ion-icon *ngIf="messageStatus(message) == 'enviar'" src="assets/images/clock-regular.svg"></ion-icon>
<ion-icon *ngIf="messageStatus(message) == 'enviado'" src="assets/images/check-solid.svg"></ion-icon>
<ion-icon *ngIf="messageStatus(message) == 'allReceived'" src="assets/images/check-double-solid.svg"></ion-icon>
<ion-icon *ngIf="messageStatus(message) == 'allViewed'" src="assets/images/check-double-solid -viewed.svg"></ion-icon>
</div>
</div>
@@ -147,7 +158,7 @@
<!-- <ion-footer (click)="ChatSystemService.getDmRoom(roomId).sendReadMessage()"> -->
<ion-footer >
<ion-footer (click)="sendReadMessage()">
<!-- <div class="typing" *ngIf="ChatSystemService.getDmRoom(roomId).otherUserType == true" >
<ngx-letters-avatar *ngIf="showAvatar"
@@ -464,3 +464,26 @@ ion-footer {
cursor: pointer;
margin: 2px;
}
.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
background: rgba(0, 0, 0, 0.5);
}
.modal img {
max-width: 90%;
max-height: 90%;
}
.modal button {
margin-top: 10px;
}
@@ -1,4 +1,4 @@
import { AfterViewInit, Component, ElementRef, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChanges, ViewChild } from '@angular/core';
import { AfterViewInit, Component, ElementRef, EventEmitter, Input, OnChanges, OnDestroy, OnInit, Output, SimpleChanges, TemplateRef, ViewChild } from '@angular/core';
import { AnimationController, GestureController, IonRange, ModalController, PopoverController } from '@ionic/angular';
import { ToastService } from 'src/app/services/toast.service';
import { ContactsPage } from '../contacts/contacts.page';
@@ -21,11 +21,10 @@ import { Howl } from 'howler';
import { ViewMediaPage } from 'src/app/modals/view-media/view-media.page';
import { PermissionService } from 'src/app/services/permission.service';
import { Observable as DexieObservable } from 'Dexie';
import { Subscription } from 'rxjs';
import { Observable, Subscription } from 'rxjs';
import { RoomLocalRepository } from 'src/app/module/chat/data/repository/room-local-repository.service'
import { MemberListLocalRepository } from 'src/app/module/chat/data/repository/member-list-local-repository.service'
import { MessageTable } from 'src/app/module/chat/infra/database/dexie/schema/message';
import { RoomListItemOutPutDTO } from 'src/app/module/chat/data/dto/room/roomListOutputDTO';
import { ChatServiceService } from 'src/app/module/chat/domain/chat-service.service';
import { EditMessagePage } from 'src/app/modals/edit-message/edit-message.page';
import { MessageEntity } from 'src/app/module/chat/domain/entity/message';
@@ -44,10 +43,10 @@ import { LastMessage } from '../../utils/lastMessage';
import { UserTypingLocalRepository } from 'src/app/module/chat/data/repository/user-typing-local-data-source.service';
import { UserTypingRemoteRepositoryService } from 'src/app/module/chat/data/repository/user-typing-live-data-source.service';
import { MessageLocalDataSourceService } from 'src/app/module/chat/data/repository/message/message-local-data-source.service';
import { MessageRemoteDataSourceService } from 'src/app/module/chat/data/repository/message/message-remote-data-source.service';
import { MessageEnum } from 'src/app/module/chat/domain/use-case/message-create-use-case.service';
import { RoomType } from "src/app/module/chat/domain/entity/group";
import { RoomTable } from 'src/app/module/chat/infra/database/dexie/schema/room';
import { Logger } from 'src/app/services/logger/main/service';
import { tap } from 'rxjs/operators';
@Component({
@@ -121,7 +120,7 @@ export class MessagesPage implements OnInit, OnChanges, AfterViewInit, OnDestroy
roomData$: DexieObservable<RoomTable | undefined>
roomStatus$: DexieObservable<Boolean >
roomMessage$: DexieObservable<MessageTable[]>
roomMembers$: DexieObservable<MemberTable[] | undefined>
roomMembers$: Observable<MemberTable[] | undefined>
//userTyping$: DexieObservable<TypingTable[] | undefined>
userTyping$: TypingTable[] | undefined
newMessagesStream!: Subscription
@@ -142,6 +141,9 @@ export class MessagesPage implements OnInit, OnChanges, AfterViewInit, OnDestroy
MessageAttachmentFileType = MessageAttachmentFileType
MessageAttachmentFileSource = MessageAttachmentSource
@ViewChild('imageModal') imageModal: TemplateRef<any>;
totalMembers = 0
constructor(
public popoverController: PopoverController,
private modalController: ModalController,
@@ -166,7 +168,6 @@ export class MessagesPage implements OnInit, OnChanges, AfterViewInit, OnDestroy
private userTypingLocalRepository: UserTypingLocalRepository,
private UserTypingRemoteRepositoryService: UserTypingRemoteRepositoryService,
private messageLocalDataSourceService: MessageLocalDataSourceService,
private MessageRemoteDataSourceService: MessageRemoteDataSourceService
) {
// update
this.checkAudioPermission()
@@ -178,21 +179,26 @@ export class MessagesPage implements OnInit, OnChanges, AfterViewInit, OnDestroy
this.roomData$ = this.RoomLocalRepository.getRoomByIdLive(this.roomId)
this.roomData$.subscribe(e => {
console.log(e)
// console.log(e)
if(e) {
this.roomType = e.roomType
}
})
this.getMessages();
this.listenToIncomingMessage();
this.listenToDeleteMessage();
this.listenToUpdateMessage();
this.listenToSendMessage()
this.listenToSendMessage();
// this.roomMessage$ = this.messageRepositoryService.getItemsLive(this.roomId)
this.roomMembers$ = this.MemberListLocalRepository.getRoomMemberByIdLive(this.roomId) as any
this.roomMembers$ = this.MemberListLocalRepository.getRoomMemberByIdLive(this.roomId).pipe(
tap((members) => {
this.totalMembers = members.length
})
)
this.roomStatus$ = this.MemberListLocalRepository.allMemberOnline(this.roomId)
this.chatServiceService.getRoomById(this.roomId)
@@ -206,6 +212,25 @@ export class MessagesPage implements OnInit, OnChanges, AfterViewInit, OnDestroy
})
}
messageStatus(message: MessageEntity) {
if(this.allViewed(message)) {
return 'allViewed'
} else if(this.allReceived(message)) {
return 'allReceived'
} else if (message.messageStatus == 'send') {
return 'enviado'
} else {
return 'enviar'
}
}
allReceived(message: MessageEntity) {
return message.info.filter(e => typeof e.deliverAt == 'string').length == this.totalMembers
}
allViewed(message: MessageEntity) {
return message.info.filter(e => typeof e.readAt == 'string').length == this.totalMembers
}
async getMessages() {
// dont remove this line
@@ -226,7 +251,6 @@ export class MessagesPage implements OnInit, OnChanges, AfterViewInit, OnDestroy
if(message.hasAttachment) {
if(message.$id) {
console.log('message.$id', message.$id)
this.chatServiceService.getMessageAttachmentByMessageId(message).then((result)=> {
if(result.isOk()) {
message.attachments[0].safeFile = result.value
@@ -253,6 +277,26 @@ export class MessagesPage implements OnInit, OnChanges, AfterViewInit, OnDestroy
async onImageError() {}
sendReadMessage() {
Logger.info('send read at')
for(const message of this.messages1[this.roomId]) {
const me = message.info.find(e => e.memberId == SessionStore.user.UserId && typeof e.readAt == 'string')
if(!me) {
this.chatServiceService.sendReadAt({
memberId: SessionStore.user.UserId,
messageId: message.id,
requestId: '',
roomId: this.roomId
})
}
}
}
listenToIncomingMessage() {
this.messageReceiveSubject?.unsubscribe();
this.messageReceiveSubject = this.chatServiceService.listenToIncomingMessage(this.roomId).subscribe(async (message) => {
@@ -302,7 +346,7 @@ export class MessagesPage implements OnInit, OnChanges, AfterViewInit, OnDestroy
const index = this.messages1[this.roomId].findIndex(e => e?.id === updateMessage.id); // Use triple equals for comparison
if (index !== -1) { // Check if the item was found
console.log('update ==')
this.messages1[this.roomId][index].info = updateMessage.info
this.messages1[this.roomId][index].message = updateMessage.message
this.messages1[this.roomId][index].reactions = updateMessage.reactions
} else {
@@ -316,19 +360,18 @@ export class MessagesPage implements OnInit, OnChanges, AfterViewInit, OnDestroy
this.messageSendSubject?.unsubscribe();
this.messageSendSubject = this.chatServiceService.listenToSendMessage(this.roomId).subscribe((updateMessage) => {
console.log('update message', updateMessage);
const index = this.messages1[this.roomId].findIndex(e => e?.requestId === updateMessage.requestId); // Use triple equals for comparison
if (index !== -1) { // Check if the item was found
console.log('update ==')
this.messages1[this.roomId][index].id = updateMessage.id
this.messages1[this.roomId][index].info = updateMessage.info
let attachmentIndex = 0;
for(const message of updateMessage.attachments) {
console.log('set attachmen id', message)
this.messages1[this.roomId][index].attachments[attachmentIndex].id = message.id
attachmentIndex++;
}
@@ -365,9 +408,9 @@ export class MessagesPage implements OnInit, OnChanges, AfterViewInit, OnDestroy
sendReadAt() {
this.chatServiceService.messageMarkAsRead({roomId: this.roomId}).then((e) => {
console.log(e)
})
// this.chatServiceService.messageMarkAsRead({roomId: this.roomId}).then((e) => {
// console.log(e)
// })
}
sendTyping() {
@@ -1240,6 +1283,11 @@ export class MessagesPage implements OnInit, OnChanges, AfterViewInit, OnDestroy
}, 1000)
}
closeModal(a: any) {
}
}
@@ -10,6 +10,7 @@ import { HttpErrorResponse } from '@angular/common/http';
import { HttpErrorHandle } from 'src/app/services/http-error-handle.service'
import { ChatServiceService } from 'src/app/module/chat/domain/chat-service.service'
import { UDate } from 'src/app/utils/date';
@Component({
selector: 'app-new-group',
@@ -124,7 +125,7 @@ export class NewGroupPage implements OnInit{
roomName: this.groupName,
createdBy: SessionStore.user.UserId,
roomType: 0,
expirationDate: this.expirationDate?.toISOString(),
expirationDate: this.expirationDate?.toISOString() ? UDate.GetDateWithTimeZone(this.expirationDate) : null,
members: []
})
@@ -162,13 +162,13 @@ export class MessagesPage implements OnInit, AfterViewInit, OnDestroy {
this.roomData$ = this.RoomLocalRepository.getRoomByIdLive(this.roomId)
this.roomData$.subscribe(e => {
console.log(e)
// console.log(e)
if(e) {
this.roomType = e.roomType
}
})
this.getMessages();
@@ -337,12 +337,13 @@ export class MessagesPage implements OnInit, AfterViewInit, OnDestroy {
if (index !== -1) { // Check if the item was found
this.messages1[this.roomId][index].id = updateMessage.id
this.messages1[this.roomId][index].info = updateMessage.info
let attachmentIndex = 0;
for(const message of updateMessage.attachments) {
for(const attachment of updateMessage.attachments) {
this.messages1[this.roomId][index].attachments[attachmentIndex].id = message.id
this.messages1[this.roomId][index].attachments[attachmentIndex].id = attachment.id
attachmentIndex++;
}
+76
View File
@@ -0,0 +1,76 @@
import { SafeResourceUrl } from "@angular/platform-browser";
import { MessageAttachmentFileType, MessageAttachmentSource } from "src/app/module/chat/data/dto/message/messageOutputDTO";
import { MessageEntity } from "src/app/module/chat/domain/entity/message";
export class MessageViewModal {
$id: number
id: string
roomId?: string
receiverId?: number
message: string
messageType: number = 0
canEdit: boolean = false
oneShot: boolean = false
sentAt: string
requireUnlock: boolean = false
info: {
memberId?: number
readAt?: string,
deliverAt?: string
}[] = []
sender: {
wxUserId: number,
wxFullName: string,
wxeMail: string,
userPhoto: string,
}
sending: boolean = false
sendAttemp = 0
attachments: {
safeFile?: SafeResourceUrl;
fileType: MessageAttachmentFileType,
source: MessageAttachmentSource,
file?: string,
fileName: string,
applicationId?: number,
docId?: string,
mimeType?: string,
description?: string
id?: string
}[] = []
reactions = []
requestId: string
status = ''
constructor(model: MessageEntity) {
Object.assign(this, model)
}
messageStatus(totalMembers: number) {
if(this.allViewed(totalMembers)) {
this.status = 'allViewed'
} else if(this.allReceived(totalMembers)) {
this.status = 'allReceived'
} else if (this.id) {
this.status = 'enviado'
} else {
this.status = 'enviar'
}
}
allReceived(totalMembers: number) {
return this.info.filter(e => typeof e.deliverAt == 'string').length == totalMembers
}
allViewed(totalMembers: number) {
return this.info.filter(e => typeof e.readAt == 'string').length == totalMembers
}
get hasAttachment() {
return this.attachments.length >= 1
}
}
+18
View File
@@ -0,0 +1,18 @@
export class UDate {
static GetDateWithTimeZone(Date: Date) {
let date = Date;
const tzOffset = -date.getTimezoneOffset(); // in minutes
const diff = tzOffset >= 0 ? '+' : '-';
const pad = (n: number) => (n < 10 ? '0' : '') + n;
return date.getFullYear() +
'-' + pad(date.getMonth() + 1) +
'-' + pad(date.getDate()) +
'T' + pad(date.getHours()) +
':' + pad(date.getMinutes()) +
':' + pad(date.getSeconds()) +
diff + pad(Math.floor(Math.abs(tzOffset) / 60)) +
':' + pad(Math.abs(tzOffset) % 60);
}
}
+108
View File
@@ -0,0 +1,108 @@
import { Injectable } from '@angular/core';
import { timer, Subscription } from 'rxjs';
/**
* Interface representing a cron job.
*/
interface CronJob {
name: string;
timeExecute: Date;
callbackFunction: () => void;
subscription: Subscription;
}
/**
* Service to manage cron jobs using RxJS. Allows scheduling, replacing, and
* removing jobs that execute a callback function at a specified time.
*/
@Injectable({
providedIn: 'root',
})
export class CronJobService {
/**
* A map to store cron jobs with a key composed of the job name and execution time.
*/
private jobs: Map<string, CronJob> = new Map();
/**
* Generates a unique key for a cron job based on its name and execution time.
* @param name - The name of the cron job.
* @param timeExecute - The time when the job should execute.
* @returns A string key combining the name and timeExecute.
*/
private generateJobKey(name: string, timeExecute: Date): string {
return `${name}_${timeExecute.getTime()}`;
}
/**
* Creates a new cron job or replaces an existing one if the job with the same
* name and execution time already exists. The job will execute the provided
* callback function at the specified time.
*
* @param name - The name of the cron job.
* @param timeExecute - The time when the job should execute.
* @param callbackFunction - The function to be called when the job executes.
*/
createCronJob(name: string, timeExecute: Date, callbackFunction: () => void) {
const now = new Date();
const delay = timeExecute.getTime() - now.getTime();
if (delay < 0) {
console.error('Time to execute is in the past!');
return;
}
const jobKey = this.generateJobKey(name, timeExecute);
// If the job already exists, replace it
if (this.jobs.has(jobKey)) {
this.removeCronJob(name, timeExecute);
}
// Create a new timer and store the subscription
const subscription = timer(delay).subscribe(() => {
callbackFunction();
this.removeCronJob(name, timeExecute);
});
this.jobs.set(jobKey, { name, timeExecute, callbackFunction, subscription });
}
/**
* Removes a cron job by its name and execution time.
*
* @param name - The name of the cron job.
* @param timeExecute - The time when the job was supposed to execute.
*/
removeCronJob(name: string, timeExecute: Date) {
const jobKey = this.generateJobKey(name, timeExecute);
const job = this.jobs.get(jobKey);
if (job) {
job.subscription.unsubscribe();
this.jobs.delete(jobKey);
}
}
/**
* Retrieves all currently scheduled cron jobs.
*
* @returns An array of all scheduled cron jobs.
*/
getCronJobs(): CronJob[] {
return Array.from(this.jobs.values());
}
/**
* Removes all cron jobs with the specified name, regardless of their execution time.
*
* @param name - The name of the cron jobs to remove.
*/
removeAllJobsByName(name: string) {
for (const [key, job] of this.jobs) {
if (job.name === name) {
job.subscription.unsubscribe();
this.jobs.delete(key);
}
}
}
}