mirror of
https://code.equilibrium.co.ao/ITO/doneit-web.git
synced 2026-04-19 04:57:52 +00:00
send and receive message
This commit is contained in:
@@ -0,0 +1,12 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { MessageAsyncService } from 'src/app/module/chat/data/async/socket/message-async.service'
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class ChatServiceService {
|
||||
|
||||
constructor(
|
||||
private MessageAsyncService: MessageAsyncService
|
||||
) { }
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { TableRoom } from "../../../data-source/room/rooom-local-data-source.service";
|
||||
import { RoomListItemOutPutDTO, RoomListOutPutDTO } from "../../../dto/room/roomListOutputDTO";
|
||||
|
||||
export function roomListDetermineChanges(serverRooms: RoomListItemOutPutDTO[], localRooms: TableRoom[]) {
|
||||
const serverRoomMap = new Map(serverRooms.map(room => [room.id, room]));
|
||||
const localRoomMap = new Map(localRooms.map(room => [room.id, room]));
|
||||
|
||||
const roomsToInsert = serverRooms.filter(room => !localRoomMap.has(room.id));
|
||||
const roomsToUpdate = serverRooms.filter(room => {
|
||||
const localRoom = localRoomMap.get(room.id);
|
||||
return localRoom && (
|
||||
room.roomName !== localRoom.roomName ||
|
||||
room.createdBy.wxUserId !== localRoom.createdBy.wxUserId ||
|
||||
room.createdAt !== localRoom.createdAt ||
|
||||
room.expirationDate !== localRoom.expirationDate ||
|
||||
room.roomType !== localRoom.roomType
|
||||
);
|
||||
});
|
||||
const roomsToDelete = localRooms.filter(room => !serverRoomMap.has(room.id));
|
||||
|
||||
return { roomsToInsert, roomsToUpdate, roomsToDelete };
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import { TableMemberList } from "../../../data-source/room/rooom-local-data-source.service";
|
||||
import { RoomByIdMemberItemOutputDTO } from "../../../dto/room/roomByIdOutputDTO";
|
||||
|
||||
export function roomMemberListDetermineChanges(____serverRooms: RoomByIdMemberItemOutputDTO[], localRooms: TableMemberList[], roomId: string) {
|
||||
|
||||
const PServerRooms: (RoomByIdMemberItemOutputDTO & {$roomIdUserId: string})[] = ____serverRooms.map( e=> {
|
||||
|
||||
return {
|
||||
...e,
|
||||
$roomIdUserId: roomId + e.user.wxUserId
|
||||
}
|
||||
})
|
||||
|
||||
const serverRoomMap = new Map(PServerRooms.map(room => [room.$roomIdUserId, room]));
|
||||
const localRoomMap = new Map(localRooms.map(room => [room.$roomIdUserId, room]));
|
||||
|
||||
const membersToInsert = PServerRooms.filter(room => !localRoomMap.has(room.$roomIdUserId));
|
||||
const membersToUpdate = PServerRooms.filter(room => {
|
||||
const localRoom = localRoomMap.get(room.$roomIdUserId);
|
||||
return localRoom && (
|
||||
room.user.wxUserId !== localRoom.user.wxUserId ||
|
||||
room.user.userPhoto !== localRoom.user.userPhoto ||
|
||||
room.joinAt !== localRoom.joinAt
|
||||
)
|
||||
});
|
||||
|
||||
const membersToDelete = localRooms.filter(room => !serverRoomMap.has(room.$roomIdUserId));
|
||||
|
||||
return { membersToInsert, membersToUpdate, membersToDelete };
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { MessageLiveDataSourceService } from '../../data-source/message/message-live-data-source.service';
|
||||
import { MessageLocalDataSourceService } from '../../data-source/message/message-local-data-source.service';
|
||||
import { MessageRemoteDataSourceService } from '../../data-source/message/message-remote-data-source.service';
|
||||
import { SignalRService } from '../../../infra/socket/signal-r.service';
|
||||
import { filter } from 'rxjs/operators';
|
||||
import { InstanceId } from '../../repository/message-respository.service';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class MessageAsyncService {
|
||||
|
||||
constructor(
|
||||
private messageRemoteDataSourceService: MessageRemoteDataSourceService,
|
||||
private messageLiveDataSourceService: MessageLiveDataSourceService,
|
||||
private messageLiveSignalRDataSourceService: SignalRService,
|
||||
private messageLocalDataSourceService: MessageLocalDataSourceService
|
||||
) {
|
||||
|
||||
|
||||
this.messageLiveSignalRDataSourceService.getMessage().pipe(
|
||||
filter((message: any) => {
|
||||
return !message?.requestId?.startsWith(InstanceId) && message?.requestId
|
||||
})
|
||||
).subscribe(async (message: any) => {
|
||||
|
||||
console.log('message async ', message)
|
||||
const id = message.id
|
||||
delete message.id;
|
||||
|
||||
// const result = await this.messageLocalDataSourceService.createMessage({
|
||||
// messageId: id,
|
||||
// sending: false,
|
||||
// ...message
|
||||
// })
|
||||
|
||||
// if(result.isOk()) {
|
||||
|
||||
// console.log(result.value)
|
||||
|
||||
// } else {
|
||||
// console.log(result.error)
|
||||
// }
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { err, ok } from 'neverthrow';
|
||||
import { WebSocketMessage, WebSocketService } from '../../../infra/socket/socket';
|
||||
import { SignalRService } from '../../../infra/socket/signal-r.service';
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class MessageLiveDataSourceService {
|
||||
|
||||
constructor(
|
||||
public socket: WebSocketService,
|
||||
private signalR: SignalRService) {}
|
||||
|
||||
async sendMessage(data: WebSocketMessage) {
|
||||
|
||||
try {
|
||||
|
||||
const result = await this.socket.sendMessage(data).toPromise()
|
||||
|
||||
return ok(result)
|
||||
} catch (e) {
|
||||
return err(e)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { err, ok } from 'neverthrow';
|
||||
import { SignalRService } from '../../../infra/socket/signal-r.service';
|
||||
|
||||
|
||||
interface msgObj {
|
||||
roomId: string;
|
||||
senderId: string;
|
||||
message:string;
|
||||
messageType:1;
|
||||
canEdit:Boolean;
|
||||
oneShot:Boolean;
|
||||
requestId: string;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class MessageLiveDataSourceService {
|
||||
|
||||
constructor(
|
||||
private messageLiveSignalRDataSourceService: SignalRService
|
||||
) {}
|
||||
|
||||
async sendMessage(data: msgObj) {
|
||||
|
||||
try {
|
||||
|
||||
const result = await this.messageLiveSignalRDataSourceService.sendMessage(data)
|
||||
|
||||
return ok(result)
|
||||
} catch (e) {
|
||||
return err(e)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,139 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Dexie, EntityTable, liveQuery } from 'Dexie';
|
||||
import { err, ok } from 'neverthrow';
|
||||
import { z } from 'zod';
|
||||
import { from, Observable, Subject } from 'rxjs';
|
||||
import { filter, switchMap } from 'rxjs/operators';
|
||||
import { MessageInputDTO } from '../../dto/message/messageInputDtO';
|
||||
|
||||
|
||||
const tableSchema = z.object({
|
||||
id: z.any().optional(),
|
||||
messageId: z.string().optional(),
|
||||
roomId: z.string().uuid(),
|
||||
senderId: z.number(),
|
||||
message: z.string(),
|
||||
messageType: z.number(),
|
||||
canEdit: z.boolean(),
|
||||
oneShot: z.boolean(),
|
||||
sentAt: z.string().optional(),
|
||||
requireUnlock: z.boolean(),
|
||||
sender: z.object({
|
||||
wxUserId: z.number(),
|
||||
wxFullName: z.string(),
|
||||
wxeMail: z.string(),
|
||||
userPhoto: z.string(),
|
||||
}),
|
||||
sending: z.boolean().optional()
|
||||
})
|
||||
|
||||
|
||||
export type TableMessage = z.infer<typeof tableSchema>
|
||||
|
||||
// Database declaration (move this to its own module also)
|
||||
export const messageDataSource = new Dexie('chat-message') as Dexie & {
|
||||
message: EntityTable<TableMessage, 'id'>;
|
||||
};
|
||||
|
||||
messageDataSource.version(1).stores({
|
||||
message: '++id, roomId, senderId, message, messageType, canEdit, oneShot, requireUnlock, messageId'
|
||||
});
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class MessageLocalDataSourceService {
|
||||
|
||||
messageSubject = new Subject();
|
||||
|
||||
constructor() {
|
||||
|
||||
messageDataSource.message.hook('creating', (primKey, obj, trans) => {
|
||||
// const newMessage = await trans.table('message').get(primKey);
|
||||
this.messageSubject.next(obj);
|
||||
// return newMessage
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
async sendMessage(data: MessageInputDTO) {
|
||||
|
||||
(data as TableMessage).sending = true
|
||||
|
||||
try {
|
||||
const result = await messageDataSource.message.add(data)
|
||||
return ok(result as string)
|
||||
} catch (e) {
|
||||
return err(false)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
async createMessage(data: MessageInputDTO) {
|
||||
|
||||
try {
|
||||
const result = await messageDataSource.message.add(data)
|
||||
return ok(result as string)
|
||||
} catch (e) {
|
||||
return err(false)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
async update(data: TableMessage ) {
|
||||
|
||||
try {
|
||||
const result = await messageDataSource.message.update(data.id, data)
|
||||
return ok(result)
|
||||
} catch (e) {
|
||||
return err(false)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
async findOrUpdate(data: TableMessage) {
|
||||
const findResult = await this.findMessageById(data.messageId)
|
||||
|
||||
if(findResult.isOk()) {
|
||||
return this.update({...findResult.value, ...data})
|
||||
} else {
|
||||
return this.createMessage(data)
|
||||
}
|
||||
}
|
||||
|
||||
getItemsLive(roomId: string) {
|
||||
return liveQuery(() =>
|
||||
messageDataSource.message.where('roomId').equals(roomId).sortBy('id')
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
async findMessageById(messageId: string) {
|
||||
try {
|
||||
const a = await messageDataSource.message.where('messageId').equals(messageId).first()
|
||||
|
||||
if(a) {
|
||||
return ok(a)
|
||||
} else {
|
||||
return err('not found')
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
return err('DB error')
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
subscribeToNewMessage(roomId: string): Observable<TableMessage> {
|
||||
return this.messageSubject.pipe(
|
||||
filter((message: TableMessage) =>
|
||||
message.roomId === roomId
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { HttpService } from 'src/app/services/http.service';
|
||||
import { MessageInputDTO, MessageInputDTOSchema } from '../../dto/message/messageInputDtO';
|
||||
import { ValidateSchema } from 'src/app/services/decorators/validate-schema.decorator';
|
||||
import { APIReturn } from 'src/app/services/decorators/api-validate-schema.decorator';
|
||||
import { MessageOutPutDTO, MessageOutPutDTOSchema } from '../../dto/message/messageOutputDTO';
|
||||
import { MessageListOutput } from '../../dto/message/messageListOutputDTO';
|
||||
import { DataSourceReturn } from 'src/app/services/Repositorys/type';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class MessageRemoteDataSourceService {
|
||||
|
||||
private baseUrl = 'https://gdapi-dev.dyndns.info/stage/api/v2/Chat'; // Your base URL
|
||||
|
||||
constructor(private httpService: HttpService) {}
|
||||
|
||||
@APIReturn(MessageOutPutDTOSchema, 'get/Messages')
|
||||
@ValidateSchema(MessageInputDTOSchema)
|
||||
async sendMessage(data: MessageInputDTO) {
|
||||
return await this.httpService.post<MessageOutPutDTO>(`${this.baseUrl}/Messages`, data);
|
||||
}
|
||||
|
||||
async reactToMessage(id: string, reaction: any) {
|
||||
return await this.httpService.post<any>(`${this.baseUrl}/Messages/${id}/React`, reaction);
|
||||
}
|
||||
|
||||
async getMessagesFromRoom(id: string): DataSourceReturn<MessageListOutput> {
|
||||
return await this.httpService.get(`${this.baseUrl}/Room/${id}/Messages`);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { WebSocketMessage, WebSocketService } from '../../../infra/socket/socket';
|
||||
import { err, ok } from 'neverthrow';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class RoomLiveDataSourceService {
|
||||
|
||||
constructor(private socket: WebSocketService) {}
|
||||
|
||||
async getRoomById(data: WebSocketMessage) {
|
||||
|
||||
try {
|
||||
|
||||
const result = await this.socket.sendMessage(data).toPromise()
|
||||
|
||||
console.log({result})
|
||||
|
||||
return ok(result)
|
||||
} catch (e) {
|
||||
return err(e)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
import { createAction, createFeatureSelector, createReducer, createSelector, on, props } from "@ngrx/store";
|
||||
import { TableRoom } from "./rooom-local-data-source.service";
|
||||
import { RoomOutPutDTO } from "../../dto/room/roomOutputDTO";
|
||||
|
||||
|
||||
export interface ChatRoom {
|
||||
[roomId: string]: TableRoom[];
|
||||
}
|
||||
|
||||
export interface RoomRemoteDataSourceState {
|
||||
chatRooms: ChatRoom;
|
||||
}
|
||||
|
||||
export const initialState: RoomRemoteDataSourceState = {
|
||||
chatRooms: {
|
||||
// add more rooms as needed
|
||||
}
|
||||
};
|
||||
|
||||
export const addMessage = createAction(
|
||||
'[Chat] Add Message',
|
||||
props<{ roomId: string; message: any }>()
|
||||
);
|
||||
|
||||
export const addRoom = createAction(
|
||||
'[Chat] Add Room',
|
||||
props<RoomOutPutDTO>()
|
||||
);
|
||||
|
||||
const _chatReducer = createReducer(
|
||||
initialState,
|
||||
on(addMessage, (state, { roomId, message }) => ({
|
||||
...state,
|
||||
chatRooms: {
|
||||
...state.chatRooms,
|
||||
[roomId]: [...(state.chatRooms[roomId] || []), message]
|
||||
}
|
||||
})),
|
||||
on(addRoom, (state, roomData: RoomOutPutDTO) => ({
|
||||
...state,
|
||||
chatRooms: {
|
||||
...state.chatRooms,
|
||||
[roomData.data.roomName]: roomData.data
|
||||
}
|
||||
}))
|
||||
);
|
||||
|
||||
export function chatReducer(state, action) {
|
||||
return _chatReducer(state, action);
|
||||
}
|
||||
|
||||
// Create a feature selector
|
||||
export const selectChatState = createFeatureSelector<RoomRemoteDataSourceState>('chat');
|
||||
|
||||
// Create a selector for a specific room's messages
|
||||
export const selectMessagesByRoom = (roomId: string) => createSelector(
|
||||
selectChatState,
|
||||
(state: RoomRemoteDataSourceState) => state.chatRooms[roomId] || []
|
||||
);
|
||||
|
||||
// Create a selector for the list of rooms
|
||||
export const selectAllRooms = createSelector(
|
||||
selectChatState,
|
||||
(state: RoomRemoteDataSourceState) => Object.keys(state.chatRooms)
|
||||
);
|
||||
@@ -0,0 +1,68 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Result } from 'neverthrow';
|
||||
import { HttpService } from 'src/app/services/http.service';
|
||||
import { RoomListOutPutDTO, RoomListOutPutDTOSchema } from '../../dto/room/roomListOutputDTO';
|
||||
import { RoomInputDTO, RoomInputDTOSchema } from '../../dto/room/roomInputDTO';
|
||||
import { RoomOutPutDTO, RoomOutPutDTOSchema } from '../../dto/room/roomOutputDTO';
|
||||
import { AddMemberToRoomInputDTO, AddMemberToRoomInputDTOSchema } from '../../dto/room/addMemberToRoomInputDto';
|
||||
import { ValidateSchema } from 'src/app/services/decorators/validate-schema.decorator';
|
||||
import { RoomByIdInputDTO, RoomByIdInputDTOSchema } from '../../dto/room/roomByIdInputDTO';
|
||||
import { RoomByIdOutputDTO, RoomByIdOutputDTOSchema } from '../../dto/room/roomByIdOutputDTO';
|
||||
import { APIReturn } from 'src/app/services/decorators/api-validate-schema.decorator';
|
||||
import { UserRemoveListInputDTO, UserRemoveListInputDTOSchema } from '../../dto/room/userRemoveListInputDTO';
|
||||
import { RoomUpdateInputDTO, RoomUpdateInputDTOSchema } from '../../dto/room/roomUpdateInputDTO';
|
||||
import { RoomUpdateOutputDTO } from '../../dto/room/roomUpdateOutputDTO';
|
||||
import { DataSourceReturn } from 'src/app/services/Repositorys/type';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class RoomRemoteDataSourceService {
|
||||
|
||||
private baseUrl = 'https://gdapi-dev.dyndns.info/stage/api/v2/Chat'; // Your base URL
|
||||
|
||||
constructor(private httpService: HttpService) {}
|
||||
|
||||
|
||||
@ValidateSchema(RoomInputDTOSchema)
|
||||
@APIReturn(RoomOutPutDTOSchema, 'post/Room')
|
||||
async createRoom(data: RoomInputDTO): DataSourceReturn<RoomOutPutDTO> {
|
||||
return await this.httpService.post<RoomOutPutDTO>(`${this.baseUrl}/Room`, data);
|
||||
}
|
||||
|
||||
|
||||
@APIReturn(RoomListOutPutDTOSchema, 'get/Room')
|
||||
async getRoomList(): Promise<DataSourceReturn<RoomListOutPutDTO>> {
|
||||
return await this.httpService.get<RoomListOutPutDTO>(`${this.baseUrl}/Room`);
|
||||
}
|
||||
|
||||
@ValidateSchema(RoomByIdInputDTOSchema)
|
||||
@APIReturn(RoomByIdOutputDTOSchema,'get/Room/${id}')
|
||||
async getRoom(id: RoomByIdInputDTO): DataSourceReturn<RoomByIdOutputDTO> {
|
||||
return await this.httpService.get(`${this.baseUrl}/Room/${id}`);
|
||||
}
|
||||
|
||||
@ValidateSchema(RoomUpdateInputDTOSchema)
|
||||
//@APIReturn(RoomByIdOutputDTOSchema,'update/Room/${id}')
|
||||
async updateRoom(data: RoomUpdateInputDTO): Promise<DataSourceReturn<RoomUpdateOutputDTO>> {
|
||||
const id = data.roomId
|
||||
delete data.roomId
|
||||
return await this.httpService.put<any>(`${this.baseUrl}/Room/${id}`, data);
|
||||
}
|
||||
|
||||
async deleteRoom(id: string): Promise<Result<any ,any>> {
|
||||
return await this.httpService.delete<any>(`${this.baseUrl}/Room/${id}`, {});
|
||||
}
|
||||
|
||||
@ValidateSchema(AddMemberToRoomInputDTOSchema)
|
||||
async addMemberToRoom(data: AddMemberToRoomInputDTO): DataSourceReturn<AddMemberToRoomInputDTO> {
|
||||
return await this.httpService.post<any>(`${this.baseUrl}/Room/${data.id}/Member`, { members:data.members });
|
||||
}
|
||||
|
||||
|
||||
@ValidateSchema(UserRemoveListInputDTOSchema)
|
||||
async removeMemberFromRoom(data: UserRemoveListInputDTO): Promise<Result<any ,any>> {
|
||||
return await this.httpService.delete<any>(`${this.baseUrl}/Room/${data.id}/Member`, {members:data.members});
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { RoomListItemOutPutDTO, RoomListOutPutDTO } from '../../dto/room/roomListOutputDTO';
|
||||
import { Dexie, EntityTable, liveQuery, Observable } from 'Dexie';
|
||||
import { err, ok, Result } from 'neverthrow';
|
||||
import { z } from 'zod';
|
||||
import { ValidateSchema } from 'src/app/services/decorators/validate-schema.decorator';
|
||||
|
||||
const tableSchema = z.object({
|
||||
id: z.string(),
|
||||
roomName: z.string(),
|
||||
createdBy: z.object({
|
||||
wxUserId: z.number(),
|
||||
wxFullName: z.string(),
|
||||
wxeMail: z.string().email(),
|
||||
userPhoto: z.string().nullable().optional()// api check
|
||||
}),
|
||||
createdAt: z.any(),
|
||||
expirationDate: z.any(),
|
||||
roomType: z.any(),
|
||||
// lastMessage: z.object({
|
||||
// sentAt: z.string(),
|
||||
// message: z.string(),
|
||||
// sender: z.object({
|
||||
// wxUserId: z.any(),
|
||||
// wxFullName: z.any(),
|
||||
// }).optional(),s
|
||||
// })
|
||||
})
|
||||
|
||||
const TableMemberListSchema = z.object({
|
||||
$roomIdUserId: z.string().optional(),
|
||||
id: z.string(),
|
||||
roomId: z.string(),
|
||||
user: z.object({
|
||||
wxUserId: z.number(),
|
||||
wxFullName: z.string(),
|
||||
wxeMail: z.string(),
|
||||
userPhoto: z.string().nullable(),
|
||||
}),
|
||||
joinAt: z.string()
|
||||
})
|
||||
|
||||
export type TableRoom = z.infer<typeof tableSchema>
|
||||
export type TableMemberList = z.infer<typeof TableMemberListSchema>
|
||||
|
||||
// Database declaration (move this to its own module also)
|
||||
export const roomDataSource = new Dexie('FriendDatabase') as Dexie & {
|
||||
room: EntityTable<TableRoom, 'id'>;
|
||||
memberList: EntityTable<TableMemberList, '$roomIdUserId'>;
|
||||
};
|
||||
|
||||
roomDataSource.version(1).stores({
|
||||
room: 'id, createdBy, roomName, roomType, expirationDate, lastMessage',
|
||||
memberList: '$roomIdUserId, id, user, joinAt, roomId',
|
||||
});
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class RoomLocalDataSourceService {
|
||||
|
||||
private baseUrl = 'https://gdapi-dev.dyndns.info/stage/api/v2/Chat'; // Your base URL
|
||||
|
||||
constructor() {}
|
||||
|
||||
@ValidateSchema(tableSchema)
|
||||
async createRoom(data: TableRoom) {
|
||||
try {
|
||||
const result = await roomDataSource.room.add(data)
|
||||
return ok(result)
|
||||
} catch (e) {
|
||||
return err(false)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async deleteRoomById(id: string) {
|
||||
try {
|
||||
const result = await roomDataSource.room.delete(id)
|
||||
return ok(result)
|
||||
} catch (e) {
|
||||
return err(false)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async leaveRoom(id: string) {
|
||||
return this.deleteRoomById(id)
|
||||
}
|
||||
|
||||
async updateRoom(data: TableRoom) {
|
||||
try {
|
||||
const result = await roomDataSource.room.update(data.id, data);
|
||||
|
||||
return ok(result)
|
||||
} catch (e) {
|
||||
return err(false)
|
||||
}
|
||||
}
|
||||
|
||||
async createOrUpdateRoom(data: TableRoom) {
|
||||
const createResult = await this.createRoom(data)
|
||||
|
||||
if(createResult.isOk()) {
|
||||
return createResult
|
||||
} else {
|
||||
return this.updateRoom(data)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async addMember(data: TableMemberList) {
|
||||
try {
|
||||
data.$roomIdUserId = data.roomId + data.user.wxUserId
|
||||
const result = await roomDataSource.memberList.add(data)
|
||||
return ok(result)
|
||||
} catch (e) {
|
||||
return err(false)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async removeMemberFromRoom($roomIdUserId): Promise<Result<any ,any>> {
|
||||
try {
|
||||
|
||||
const member = await roomDataSource.memberList.where({ $roomIdUserId: $roomIdUserId }).first();
|
||||
|
||||
if (member) {
|
||||
const result = await ok(roomDataSource.memberList.delete($roomIdUserId));
|
||||
console.log(`Member with $roomIdUserId ${$roomIdUserId} removed from room ${member.roomId}.`);
|
||||
return result
|
||||
} else {
|
||||
console.log(`No member found with $roomIdUserId ${$roomIdUserId}`);
|
||||
return err('not Found')
|
||||
}
|
||||
|
||||
} catch (e) {
|
||||
return err(false)
|
||||
}
|
||||
}
|
||||
|
||||
async getRoomById(id: any) {
|
||||
try {
|
||||
const result = await roomDataSource.room.get(id)
|
||||
return ok(result)
|
||||
} catch (e) {
|
||||
return err(false)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
async getRoomList() {
|
||||
return await roomDataSource.room.toArray()
|
||||
}
|
||||
|
||||
|
||||
getItemsLive(): Observable<RoomListOutPutDTO[]> {
|
||||
return liveQuery(() => roomDataSource.room.toArray()) as any;
|
||||
}
|
||||
|
||||
getRoomByIdLive(id: any): Observable<RoomListItemOutPutDTO | undefined> {
|
||||
return liveQuery(() => roomDataSource.room.get(id)) as any;
|
||||
}
|
||||
|
||||
async getRoomMemberById(roomId: any) {
|
||||
return await roomDataSource.memberList.where('roomId').equals(roomId).toArray()
|
||||
}
|
||||
getRoomMemberByIdLive(roomId: any) {
|
||||
return liveQuery(() => roomDataSource.memberList.where('roomId').equals(roomId).toArray())
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const MessageInputDTOSchema = z.object({
|
||||
roomId: z.string().uuid(),
|
||||
senderId: z.number(),
|
||||
message: z.string(),
|
||||
messageType: z.number(),
|
||||
canEdit: z.boolean(),
|
||||
oneShot: z.boolean(),
|
||||
requireUnlock: z.boolean(),
|
||||
});
|
||||
|
||||
|
||||
export type MessageInputDTO = z.infer<typeof MessageInputDTOSchema>
|
||||
@@ -0,0 +1,7 @@
|
||||
import { z } from "zod"
|
||||
|
||||
const MessageListInputDTOSchema = z.object({
|
||||
roomId: z.string(),
|
||||
});
|
||||
|
||||
export type MessageListInputDTO = z.infer<typeof MessageListInputDTOSchema>
|
||||
@@ -0,0 +1,29 @@
|
||||
import { z } from "zod"
|
||||
|
||||
const SenderSchema = z.object({
|
||||
wxUserId: z.number(),
|
||||
wxFullName: z.string(),
|
||||
wxeMail: z.string(),
|
||||
userPhoto: z.string()
|
||||
});
|
||||
|
||||
const DataSchema = z.object({
|
||||
id: z.string(),
|
||||
sender: SenderSchema,
|
||||
message: z.string(),
|
||||
messageType: z.number(),
|
||||
sentAt: z.string().datetime(),
|
||||
deliverAt: z.string().datetime().nullable(),
|
||||
canEdit: z.boolean(),
|
||||
oneShot: z.boolean(),
|
||||
requireUnlock: z.boolean()
|
||||
});
|
||||
|
||||
const MessageListOutputSchema = z.object({
|
||||
success: z.boolean(),
|
||||
message: z.string().nullable(),
|
||||
data: z.array(DataSchema)
|
||||
});
|
||||
|
||||
|
||||
export type MessageListOutput = z.infer<typeof MessageListOutputSchema>
|
||||
@@ -0,0 +1,29 @@
|
||||
import { z } from "zod"
|
||||
|
||||
const DataSchema = z.object({
|
||||
id: z.string(),
|
||||
chatRoomId: z.string(),
|
||||
wxUserId: z.number(),
|
||||
sender: z.object({
|
||||
wxUserId: z.number(),
|
||||
wxFullName: z.string(),
|
||||
wxeMail: z.string(),
|
||||
userPhoto: z.string().optional()
|
||||
}).nullable(),
|
||||
message: z.string(),
|
||||
messageType: z.number(),
|
||||
sentAt: z.string(),
|
||||
deliverAt: z.string().datetime().nullable(),
|
||||
canEdit: z.boolean(),
|
||||
oneShot: z.boolean(),
|
||||
requireUnlock: z.boolean()
|
||||
});
|
||||
|
||||
|
||||
export const MessageOutPutDTOSchema = z.object({
|
||||
success: z.boolean(),
|
||||
message: z.string(),
|
||||
data: DataSchema
|
||||
});
|
||||
|
||||
export type MessageOutPutDTO = z.infer<typeof MessageOutPutDTOSchema>
|
||||
@@ -0,0 +1,8 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const AddMemberToRoomInputDTOSchema = z.object({
|
||||
id: z.string(),
|
||||
members: z.array(z.number()),
|
||||
});
|
||||
|
||||
export type AddMemberToRoomInputDTO = z.infer<typeof AddMemberToRoomInputDTOSchema>
|
||||
@@ -0,0 +1,9 @@
|
||||
import { z } from "zod";
|
||||
|
||||
const OutPutDTOSchema = z.object({
|
||||
success: z.string(),
|
||||
message: z.string(),
|
||||
data: z.any()
|
||||
});
|
||||
|
||||
export type OutPutDTO = z.infer<typeof OutPutDTOSchema>
|
||||
@@ -0,0 +1,6 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const RoomByIdInputDTOSchema = z.string()
|
||||
|
||||
|
||||
export type RoomByIdInputDTO = z.infer<typeof RoomByIdInputDTOSchema>
|
||||
@@ -0,0 +1,32 @@
|
||||
import { z } from "zod";
|
||||
|
||||
const UserSchema = z.object({
|
||||
wxUserId: z.number(),
|
||||
wxFullName: z.string(),
|
||||
wxeMail: z.string(),
|
||||
userPhoto: z.string().nullable(),
|
||||
});
|
||||
|
||||
const MemberSchema = z.object({
|
||||
id: z.string(),
|
||||
user: UserSchema,
|
||||
joinAt: z.string(),
|
||||
});
|
||||
|
||||
export const RoomByIdOutputDTOSchema = z.object({
|
||||
success: z.boolean(),
|
||||
message: z.string(),
|
||||
data: z.object({
|
||||
id: z.string(),
|
||||
roomName: z.string(),
|
||||
createdBy: UserSchema,
|
||||
createdAt: z.string(),
|
||||
expirationDate: z.string().nullable(),
|
||||
roomType: z.number(),
|
||||
members: z.array(MemberSchema),
|
||||
}),
|
||||
})
|
||||
|
||||
|
||||
export type RoomByIdMemberItemOutputDTO = z.infer<typeof MemberSchema>
|
||||
export type RoomByIdOutputDTO = z.infer<typeof RoomByIdOutputDTOSchema>
|
||||
@@ -0,0 +1,12 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const RoomInputDTOSchema = z.object({
|
||||
roomName: z.string(),
|
||||
createdBy: z.number(),
|
||||
roomType: z.number(),
|
||||
expirationDate: z.string().datetime().nullable(),
|
||||
members: z.array(z.number())
|
||||
});
|
||||
|
||||
|
||||
export type RoomInputDTO = z.infer<typeof RoomInputDTOSchema>
|
||||
@@ -0,0 +1,29 @@
|
||||
import { z } from "zod";
|
||||
|
||||
const CreatedBySchema = z.object({
|
||||
wxUserId: z.number(),
|
||||
wxFullName: z.string(),
|
||||
wxeMail: z.string().email(),
|
||||
userPhoto: z.string().nullable()// api check
|
||||
});
|
||||
|
||||
const RoomListItemOutPutDTOSchema = z.object({
|
||||
id: z.string(),
|
||||
roomName: z.string(),
|
||||
createdBy: CreatedBySchema,
|
||||
createdAt: z.string(),
|
||||
expirationDate: z.string().nullable(), // api check
|
||||
roomType: z.number()
|
||||
})
|
||||
|
||||
|
||||
// Define the schema for the entire response
|
||||
export const RoomListOutPutDTOSchema = z.object({
|
||||
success: z.boolean(),
|
||||
message: z.string(),
|
||||
data: z.array(RoomListItemOutPutDTOSchema),
|
||||
});
|
||||
|
||||
export type RoomListItemOutPutDTO = z.infer<typeof RoomListItemOutPutDTOSchema>
|
||||
|
||||
export type RoomListOutPutDTO = z.infer<typeof RoomListOutPutDTOSchema>
|
||||
@@ -0,0 +1,16 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const RoomOutPutDTOSchema = z.object({
|
||||
success: z.boolean(),
|
||||
message: z.string(),
|
||||
data: z.object({
|
||||
id: z.string(),
|
||||
roomName: z.string(),
|
||||
createdBy: z.any().nullable(),
|
||||
createdAt: z.string(),
|
||||
expirationDate: z.string().nullable(),
|
||||
roomType: z.any()
|
||||
})
|
||||
});
|
||||
|
||||
export type RoomOutPutDTO = z.infer<typeof RoomOutPutDTOSchema>
|
||||
@@ -0,0 +1,9 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const RoomUpdateInputDTOSchema = z.object({
|
||||
roomName: z.string(),
|
||||
roomId: z.string(),
|
||||
roomType: z.number(),
|
||||
});
|
||||
|
||||
export type RoomUpdateInputDTO = z.infer<typeof RoomUpdateInputDTOSchema>
|
||||
@@ -0,0 +1,30 @@
|
||||
import { z } from "zod";
|
||||
|
||||
const UserSchema = z.object({
|
||||
wxUserId: z.number(),
|
||||
wxFullName: z.string(),
|
||||
wxeMail: z.string(),
|
||||
userPhoto: z.string().nullable(),
|
||||
});
|
||||
|
||||
const MemberSchema = z.object({
|
||||
id: z.string(),
|
||||
user: UserSchema,
|
||||
joinAt: z.string(),
|
||||
});
|
||||
|
||||
export const RoomUpdateOutputDTOSchema = z.object({
|
||||
success: z.boolean(),
|
||||
message: z.string(),
|
||||
data: z.object({
|
||||
id: z.string(),
|
||||
roomName: z.string(),
|
||||
createdBy: UserSchema,
|
||||
createdAt: z.string(),
|
||||
expirationDate: z.string().nullable(),
|
||||
roomType: z.number(),
|
||||
members: z.array(MemberSchema),
|
||||
}),
|
||||
});
|
||||
|
||||
export type RoomUpdateOutputDTO = z.infer<typeof RoomUpdateOutputDTOSchema>
|
||||
@@ -0,0 +1,9 @@
|
||||
import { z } from "zod";
|
||||
|
||||
// Define the schema for the entire response
|
||||
export const UserRemoveListInputDTOSchema = z.object({
|
||||
id: z.string(),
|
||||
members: z.array(z.number())
|
||||
});
|
||||
|
||||
export type UserRemoveListInputDTO = z.infer<typeof UserRemoveListInputDTOSchema>
|
||||
@@ -0,0 +1,103 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { MessageRemoteDataSourceService } from '../data-source/message/message-remote-data-source.service';
|
||||
import { MessageLiveDataSourceService } from '../data-source/message/message-live-data-source.service';
|
||||
import { MessageInputDTO } from '../dto/message/messageInputDtO';
|
||||
import { MessageLocalDataSourceService, TableMessage } from '../data-source/message/message-local-data-source.service';
|
||||
import { SessionStore } from 'src/app/store/session.service';
|
||||
import { SignalRService } from '../../infra/socket/signal-r.service';
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
export const InstanceId = uuidv4();
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class MessageRepositoryService {
|
||||
|
||||
constructor(
|
||||
private messageRemoteDataSourceService: MessageRemoteDataSourceService,
|
||||
private messageLiveDataSourceService: MessageLiveDataSourceService,
|
||||
private messageLiveSignalRDataSourceService: SignalRService,
|
||||
private messageLocalDataSourceService: MessageLocalDataSourceService
|
||||
) {
|
||||
this.messageLiveDataSourceService.socket.messages$.subscribe(({payload, requestId, type}) => {
|
||||
|
||||
|
||||
|
||||
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
async sendMessage(data: MessageInputDTO) {
|
||||
|
||||
(data as TableMessage).sender = {
|
||||
userPhoto: '',
|
||||
wxeMail: SessionStore.user.Email,
|
||||
wxFullName: SessionStore.user.FullName,
|
||||
wxUserId: SessionStore.user.UserId
|
||||
}
|
||||
|
||||
data['requestId'] = InstanceId +'@'+ uuidv4();
|
||||
|
||||
const localActionResult = await this.messageLocalDataSourceService.sendMessage(data)
|
||||
|
||||
this.messageLiveDataSourceService.sendMessage({
|
||||
type: 'sendMessage',
|
||||
payload: data
|
||||
})
|
||||
|
||||
if(localActionResult.isOk()) {
|
||||
|
||||
const sendMessageResult = await this.messageLiveSignalRDataSourceService.sendMessage(data)
|
||||
|
||||
|
||||
//const sendMessageResult = await this.messageRemoteDataSourceService.sendMessage(data)
|
||||
|
||||
if(sendMessageResult.isOk()) {
|
||||
|
||||
if(sendMessageResult.value.sender == undefined || sendMessageResult.value.sender == null) {
|
||||
console.log({sendMessageResult})
|
||||
delete sendMessageResult.value.sender
|
||||
}
|
||||
|
||||
let clone: TableMessage = {
|
||||
...sendMessageResult.value,
|
||||
messageId: sendMessageResult.value.id,
|
||||
id : localActionResult.value
|
||||
}
|
||||
|
||||
return this.messageLocalDataSourceService.update({...clone, sending: false})
|
||||
}
|
||||
|
||||
} else {
|
||||
return this.messageLocalDataSourceService.update({sending: false})
|
||||
}
|
||||
}
|
||||
|
||||
async listAllMessagesByRoomId(id: string) {
|
||||
const result = await this.messageRemoteDataSourceService.getMessagesFromRoom(id)
|
||||
|
||||
if(result.isOk()) {
|
||||
|
||||
for(const message of result.value.data) {
|
||||
let clone: TableMessage = message
|
||||
clone.messageId = message.id
|
||||
clone.roomId = id
|
||||
delete clone.id
|
||||
|
||||
await this.messageLocalDataSourceService.findOrUpdate(clone)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
getItemsLive (roomId: string) {
|
||||
return this.messageLocalDataSourceService.getItemsLive(roomId)
|
||||
}
|
||||
|
||||
|
||||
subscribeToNewMessages(roomId: any) {
|
||||
return this.messageLocalDataSourceService.subscribeToNewMessage(roomId)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,299 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { RoomRemoteDataSourceService } from '../data-source/room/room-remote-data-source.service'
|
||||
import { RoomInputDTO } from '../dto/room/roomInputDTO';
|
||||
import { AddMemberToRoomInputDTO } from '../dto/room/addMemberToRoomInputDto';
|
||||
import { RoomLocalDataSourceService } from '../data-source/room/rooom-local-data-source.service';
|
||||
import { RoomByIdInputDTO } from '../dto/room/roomByIdInputDTO';
|
||||
import { roomListDetermineChanges } from '../async/list/rooms/roomListChangeDetector';
|
||||
import { UserRemoveListInputDTO } from '../dto/room/userRemoveListInputDTO';
|
||||
import { roomMemberListDetermineChanges } from '../async/list/rooms/roomMembersChangeDetector';
|
||||
import { captureAndReraiseAsync } from 'src/app/services/decorators/captureAndReraiseAsync';
|
||||
import { RoomUpdateInputDTO } from '../dto/room/roomUpdateInputDTO';
|
||||
import { SessionStore } from 'src/app/store/session.service';
|
||||
import { RoomLiveDataSourceService } from '../data-source/room/room-live-data-source.service';
|
||||
import { isHttpResponse } from 'src/app/services/http.service';
|
||||
import { MessageLiveDataSourceService } from '../data-source/message/message-live-data-source.service';
|
||||
|
||||
|
||||
function date(isoDateString) {
|
||||
let date = new Date(isoDateString);
|
||||
|
||||
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);
|
||||
}
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class RoomRepositoryService {
|
||||
|
||||
constructor(
|
||||
private roomRemoteDataSourceService: RoomRemoteDataSourceService,
|
||||
// private roomMemoryDataSourceService: Store<RoomRemoteDataSourceState>,
|
||||
private roomLocalDataSourceService: RoomLocalDataSourceService,
|
||||
private roomLiveDataSourceService: RoomLiveDataSourceService,
|
||||
private messageLiveDataSourceService: MessageLiveDataSourceService,
|
||||
) {
|
||||
|
||||
// this.messageLiveDataSourceService.socket.messages$.subscribe(({payload, requestId, type}) => {
|
||||
// if(payload.sender == null) {
|
||||
// delete payload.sender
|
||||
// }
|
||||
|
||||
// if(type == 'sendMessage') {
|
||||
// let clone: TableMessage = {
|
||||
// ...payload,
|
||||
// messageId: payload.id,
|
||||
// }
|
||||
|
||||
// this.roomLocalDataSourceService.updateRoom({lastMessage: clone})
|
||||
|
||||
// }
|
||||
|
||||
// })
|
||||
|
||||
}
|
||||
|
||||
@captureAndReraiseAsync('RoomRepositoryService/list')
|
||||
async list() {
|
||||
const result = await this.roomRemoteDataSourceService.getRoomList()
|
||||
|
||||
const localList = await this.roomLocalDataSourceService.getRoomList()
|
||||
|
||||
if(result.isOk()) {
|
||||
const { roomsToDelete, roomsToInsert, roomsToUpdate } = roomListDetermineChanges(result.value.data, localList)
|
||||
|
||||
for( const roomData of roomsToInsert) {
|
||||
|
||||
// roomData["lastMessage"] = {
|
||||
// sentAt: date(roomData.createdAt),
|
||||
// message: "",
|
||||
// sender: {
|
||||
// wxUserId: "",
|
||||
// wxFullName: "",
|
||||
// },
|
||||
// }
|
||||
|
||||
this.roomLocalDataSourceService.createRoom(roomData)
|
||||
}
|
||||
|
||||
for( const roomData of roomsToUpdate) {
|
||||
|
||||
// roomData["lastMessage"] = {
|
||||
// sentAt: date(roomData.createdAt),
|
||||
// message: "",
|
||||
// sender: {
|
||||
// wxUserId: "",
|
||||
// wxFullName: "",
|
||||
// },
|
||||
// }
|
||||
|
||||
this.roomLocalDataSourceService.updateRoom(roomData)
|
||||
}
|
||||
|
||||
for( const roomData of roomsToDelete) {
|
||||
|
||||
// roomData["lastMessage"] = {
|
||||
// sentAt: date(roomData.createdAt),
|
||||
// message: "",
|
||||
// sender: {
|
||||
// wxUserId: 0,
|
||||
// wxFullName: "",
|
||||
// },
|
||||
// }
|
||||
|
||||
this.roomLocalDataSourceService.deleteRoomById(roomData.id)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@captureAndReraiseAsync('RoomRepositoryService/updateRoomBy')
|
||||
async updateRoomBy(data: RoomUpdateInputDTO) {
|
||||
|
||||
const result = await this.roomRemoteDataSourceService.updateRoom(data)
|
||||
|
||||
if(result.isOk()) {
|
||||
const localList = await this.roomLocalDataSourceService.getRoomList()
|
||||
const { roomsToDelete, roomsToInsert, roomsToUpdate } = roomListDetermineChanges([result.value.data], localList)
|
||||
|
||||
for( const roomData of roomsToUpdate) {
|
||||
if(!roomData.createdBy?.wxUserId) {
|
||||
delete roomData.createdBy;
|
||||
}
|
||||
|
||||
this.roomLocalDataSourceService.updateRoom(roomData)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@captureAndReraiseAsync('RoomRepositoryService/getRoomById')
|
||||
async getRoomById(id: RoomByIdInputDTO) {
|
||||
const result = await this.roomRemoteDataSourceService.getRoom(id)
|
||||
|
||||
if(result.isOk()) {
|
||||
|
||||
const localListRoom = await this.roomLocalDataSourceService.getRoomList()
|
||||
const { roomsToDelete, roomsToInsert, roomsToUpdate } = roomListDetermineChanges([result.value.data], localListRoom)
|
||||
|
||||
for( const roomData of roomsToUpdate) {
|
||||
this.roomLocalDataSourceService.updateRoom(roomData)
|
||||
}
|
||||
|
||||
// ============================
|
||||
const localList = await this.roomLocalDataSourceService.getRoomMemberById(id)
|
||||
|
||||
const { membersToInsert, membersToUpdate, membersToDelete } = roomMemberListDetermineChanges(result.value.data.members, localList, id)
|
||||
|
||||
for (const user of membersToInsert) {
|
||||
await this.roomLocalDataSourceService.addMember({...user, roomId:id})
|
||||
}
|
||||
|
||||
for(const user of membersToDelete) {
|
||||
await this.roomLocalDataSourceService.removeMemberFromRoom(user.$roomIdUserId)
|
||||
}
|
||||
|
||||
const __localListRoom = await this.roomLocalDataSourceService.getRoomList()
|
||||
|
||||
// this.roomLiveDataSourceService.getRoomById({
|
||||
// type:'memberList',
|
||||
// payload: __localListRoom
|
||||
// })
|
||||
|
||||
} else if (isHttpResponse(result.error) ) {
|
||||
if(result.error.status == 404) {
|
||||
await this.roomLocalDataSourceService.deleteRoomById(id)
|
||||
}
|
||||
// this.httpErrorHandle.httpStatusHandle(result.error)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@captureAndReraiseAsync('RoomRepositoryService/deleteRoomById')
|
||||
async deleteRoomById(id: RoomByIdInputDTO) {
|
||||
const result = await this.roomRemoteDataSourceService.deleteRoom(id)
|
||||
|
||||
if(result.isOk()) {
|
||||
|
||||
const result = await this.roomLocalDataSourceService.deleteRoomById(id)
|
||||
this.messageLiveDataSourceService.sendMessage({
|
||||
type: 'createRoom',
|
||||
payload: {a: '5'}
|
||||
})
|
||||
|
||||
return result
|
||||
} else if (isHttpResponse(result.error)) {
|
||||
if(result.error.status == 404) {
|
||||
await this.roomLocalDataSourceService.deleteRoomById(id)
|
||||
}
|
||||
// this.httpErrorHandle.httpStatusHandle(result.error)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
@captureAndReraiseAsync('RoomRepositoryService/create')
|
||||
async create(data: RoomInputDTO) {
|
||||
|
||||
const result = await this.roomRemoteDataSourceService.createRoom(data)
|
||||
if(result.isOk()) {
|
||||
|
||||
if(!result.value.data.createdBy) {
|
||||
|
||||
let dataObject;
|
||||
|
||||
result.value.data.createdBy = {
|
||||
wxeMail: SessionStore.user.Email,
|
||||
wxFullName: SessionStore.user.FullName,
|
||||
wxUserId: SessionStore.user.UserId,
|
||||
}
|
||||
|
||||
dataObject = result.value.data
|
||||
dataObject.lastMessage = {
|
||||
sentAt: date(new Date()),
|
||||
message: "",
|
||||
sender: {
|
||||
wxUserId: "",
|
||||
wxFullName: "",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// this.roomMemoryDataSourceService.dispatch(addRoom(result.value))
|
||||
const localResult = await this.roomLocalDataSourceService.createRoom(result.value.data)
|
||||
|
||||
this.messageLiveDataSourceService.sendMessage({
|
||||
type: 'createRoom',
|
||||
payload: {a: '5'}
|
||||
})
|
||||
|
||||
return localResult.map(e => result.value)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
@captureAndReraiseAsync('RoomRepositoryService/addMemberToRoom')
|
||||
async addMemberToRoom(data: AddMemberToRoomInputDTO) {
|
||||
const result = await this.roomRemoteDataSourceService.addMemberToRoom(data)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
@captureAndReraiseAsync('RoomRepositoryService/removeMemberToRoom')
|
||||
async removeMemberToRoom(data: UserRemoveListInputDTO) {
|
||||
const result = await this.roomRemoteDataSourceService.removeMemberFromRoom(data)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
|
||||
async leaveRoom(data: UserRemoveListInputDTO) {
|
||||
const result = await this.roomRemoteDataSourceService.removeMemberFromRoom(data)
|
||||
|
||||
if(result.isOk()) {
|
||||
this.roomLocalDataSourceService.leaveRoom(data.id)
|
||||
} else if (isHttpResponse(result.error)) {
|
||||
if(result.error.status == 404) {
|
||||
await this.roomLocalDataSourceService.deleteRoomById(data.id)
|
||||
|
||||
}
|
||||
// this.httpErrorHandle.httpStatusHandle(result.error)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
getItemsLive() {
|
||||
return this.roomLocalDataSourceService.getItemsLive()
|
||||
}
|
||||
|
||||
getItemByIdLive(id: any) {
|
||||
return this.roomLocalDataSourceService.getRoomByIdLive(id)
|
||||
}
|
||||
|
||||
|
||||
getRoomMemberByIdLive(roomId: any) {
|
||||
return this.roomLocalDataSourceService.getRoomMemberByIdLive(roomId)
|
||||
}
|
||||
|
||||
getRoomMemberById(roomId: any) {
|
||||
return this.roomLocalDataSourceService.getRoomMemberById(roomId)
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import { TestBed } from '@angular/core/testing';
|
||||
|
||||
import { SignalRService } from './signal-r.service';
|
||||
|
||||
describe('SignalRService', () => {
|
||||
let service: SignalRService;
|
||||
|
||||
beforeEach(() => {
|
||||
TestBed.configureTestingModule({});
|
||||
service = TestBed.inject(SignalRService);
|
||||
});
|
||||
|
||||
it('should be created', () => {
|
||||
expect(service).toBeTruthy();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,78 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { BehaviorSubject } from 'rxjs';
|
||||
import { Platform } from '@ionic/angular';
|
||||
import { SignalRConnection } from './signalR';
|
||||
import { Plugins } from '@capacitor/core';
|
||||
|
||||
const { App } = Plugins;
|
||||
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class SignalRService {
|
||||
private connection: SignalRConnection;
|
||||
private messageSubject: BehaviorSubject<string> = new BehaviorSubject<any>(null);
|
||||
private connectingSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(null);
|
||||
|
||||
constructor(
|
||||
private platform: Platform,) {
|
||||
// this.startConnection();
|
||||
// this.addMessageListener();
|
||||
|
||||
try {
|
||||
if (!this.platform.is('desktop')) {
|
||||
App.addListener('appStateChange', ({ isActive }) => {
|
||||
if (isActive) {
|
||||
// The app is in the foreground.
|
||||
console.log('App is in the foreground');
|
||||
|
||||
this.newConnection()
|
||||
|
||||
} else {
|
||||
// The app is in the background.
|
||||
console.log('App is in the background');
|
||||
// You can perform actions specific to the background state here.
|
||||
}
|
||||
});
|
||||
}
|
||||
} catch(error) {}
|
||||
|
||||
this.establishConnection()
|
||||
}
|
||||
|
||||
private async establishConnection() {
|
||||
|
||||
const connection = new SignalRConnection({url:'https://gdapi-dev.dyndns.info/stage/chathub'})
|
||||
const attempConnection = await connection.establishConnection()
|
||||
|
||||
if(attempConnection.isOk()) {
|
||||
|
||||
this.connection?.closeConnection()
|
||||
this.connection = connection
|
||||
|
||||
this.connection.getSendLater().subscribe(data => {
|
||||
|
||||
})
|
||||
|
||||
this.connection.getMessages().subscribe((data) => {
|
||||
this.messageSubject.next(data)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
getMessage() {
|
||||
return this.messageSubject.asObservable()
|
||||
|
||||
}
|
||||
|
||||
async sendMessage(data: Object) {
|
||||
return await this.connection.sendMessage(data as any)
|
||||
}
|
||||
|
||||
newConnection() {
|
||||
this.establishConnection()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,140 @@
|
||||
import * as signalR from '@microsoft/signalr';
|
||||
import { BehaviorSubject, Observable } from 'rxjs';
|
||||
import { ok, Result, err } from 'neverthrow';
|
||||
import { SessionStore } from 'src/app/store/session.service';
|
||||
import { filter, first } from 'rxjs/operators';
|
||||
|
||||
export class SignalRConnection {
|
||||
|
||||
private hubConnection: signalR.HubConnection;
|
||||
private messageSubject: BehaviorSubject<string> = new BehaviorSubject<any>(null);
|
||||
private connectionStateSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
||||
private disconnectSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
||||
private reconnectSubject: BehaviorSubject<boolean> = new BehaviorSubject<boolean>(false);
|
||||
private sendLaterSubject: BehaviorSubject<Object> = new BehaviorSubject<Object>(false);
|
||||
private reconnect = true
|
||||
url: string
|
||||
|
||||
constructor({url}) {
|
||||
this.url = url
|
||||
}
|
||||
|
||||
establishConnection(): Promise<Result<signalR.HubConnection, false>> {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
const hubConnection = new signalR.HubConnectionBuilder()
|
||||
.withUrl(this.url)
|
||||
.build();
|
||||
|
||||
this.hubConnection = hubConnection
|
||||
this.join()
|
||||
|
||||
hubConnection
|
||||
.start()
|
||||
.then(() => {
|
||||
console.log('Connection started');
|
||||
this.connectionStateSubject.next(true);
|
||||
this.hubConnection = hubConnection
|
||||
this.addMessageListener()
|
||||
this.join()
|
||||
resolve(ok(hubConnection))
|
||||
})
|
||||
.catch(error => {
|
||||
console.log('Error while starting connection: ' + error);
|
||||
this.connectionStateSubject.next(false);
|
||||
reject(err(false))
|
||||
});
|
||||
|
||||
hubConnection.onclose(() => {
|
||||
console.log('Connection closed');
|
||||
this.connectionStateSubject.next(false);
|
||||
this.disconnectSubject.next(true)
|
||||
if(this.reconnect) {
|
||||
this.attempReconnect();
|
||||
}
|
||||
|
||||
});
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
async attempReconnect() {
|
||||
const attempConnection = await this.establishConnection()
|
||||
|
||||
if(attempConnection.isOk()) {
|
||||
this.reconnectSubject.next(true)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public join() {
|
||||
if(this.connectionStateSubject.value == true) {
|
||||
console.log('join=============')
|
||||
this.hubConnection.invoke("Join", 105, "UserFirefox");
|
||||
} else {
|
||||
this.sendLaterSubject.next({method: 'SendMessage', args:["Join", 312, "Daniel"]})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public async sendMessage(data: Object & { requestId}):Promise<Result<any, any>> {
|
||||
return new Promise((resolve, reject) => {
|
||||
|
||||
if(this.connectionStateSubject.value == true) {
|
||||
console.log('sendMessage')
|
||||
this.hubConnection.invoke("SendMessage", data)
|
||||
|
||||
this.messageSubject.pipe(
|
||||
filter((message: any) => data.requestId == message?.requestId),
|
||||
first()
|
||||
).subscribe(value => {
|
||||
resolve(ok(value))
|
||||
console.log('Received valid value:', value);
|
||||
});
|
||||
|
||||
} else {
|
||||
this.sendLaterSubject.next({method: 'SendMessage', args: data})
|
||||
return reject(err(false))
|
||||
}
|
||||
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
private addMessageListener(): void {
|
||||
this.hubConnection.on('ReceiveMessage', (message) => {
|
||||
console.log('ReceiveMessage', message)
|
||||
this.messageSubject.next(message);
|
||||
});
|
||||
}
|
||||
|
||||
public getMessages(): Observable<string> {
|
||||
return this.messageSubject.asObservable()
|
||||
}
|
||||
|
||||
public getConnectionState(): Observable<boolean> {
|
||||
return this.connectionStateSubject.asObservable();
|
||||
}
|
||||
|
||||
public getDisconnectTrigger(): Observable<boolean> {
|
||||
return this.disconnectSubject.asObservable();
|
||||
}
|
||||
|
||||
public getSendLater() {
|
||||
return this.sendLaterSubject.asObservable();
|
||||
}
|
||||
|
||||
public closeConnection(): void {
|
||||
this.reconnect = false
|
||||
if (this.hubConnection) {
|
||||
this.hubConnection
|
||||
.stop()
|
||||
.then(() => {
|
||||
console.log('Connection closed by user');
|
||||
this.connectionStateSubject.next(false);
|
||||
})
|
||||
.catch(err => console.log('Error while closing connection: ' + err));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>SignalR Client</title>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/8.0.0/signalr.min.js"></script>
|
||||
</head>
|
||||
<body>
|
||||
<h1>SignalR Client</h1>
|
||||
<div id="messages"></div>
|
||||
<input id="chatbox">
|
||||
<script>
|
||||
var msgObj = {
|
||||
roomId: "882fcb86-4028-4242-bb47-fca0170dcc65",
|
||||
senderId:105,
|
||||
message:"message enviada",
|
||||
messageType:1,
|
||||
canEdit:true,
|
||||
oneShot:false,
|
||||
};
|
||||
|
||||
const connection = new signalR.HubConnectionBuilder()
|
||||
.withAutomaticReconnect()
|
||||
.withUrl("https://gdapi-dev.dyndns.info/stage/chathub")
|
||||
.build();
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
connection.start().then(function () {
|
||||
connection.invoke("Join", 105, "UserFirefox");
|
||||
document.getElementById("chatbox").addEventListener("keyup", function (event) {
|
||||
if (event.key === "Enter") {
|
||||
msgObj.Message = chatbox.value;
|
||||
connection.invoke("SendMessage", msgObj);
|
||||
event.target.value = "";
|
||||
}
|
||||
});
|
||||
}).catch(function (err) {
|
||||
return console.error(err.toString());
|
||||
});
|
||||
|
||||
connection.on("ReceiveMessage", function (message) {
|
||||
console.log(message);
|
||||
const messages = document.getElementById("messages");
|
||||
messages.innerHTML += `<p>${message.message}</p>`;
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,129 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable, Subject, BehaviorSubject } from 'rxjs';
|
||||
import { webSocket, WebSocketSubject } from 'rxjs/webSocket';
|
||||
import { catchError, retryWhen, tap, delay } from 'rxjs/operators';
|
||||
import { SessionStore } from 'src/app/store/session.service';
|
||||
import { v4 as uuidv4 } from 'uuid'
|
||||
|
||||
|
||||
export interface WebSocketMessage {
|
||||
type: string;
|
||||
payload: any;
|
||||
requestId?: string;
|
||||
}
|
||||
|
||||
interface WebSocketError {
|
||||
type: string;
|
||||
error: any;
|
||||
}
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root'
|
||||
})
|
||||
export class WebSocketService {
|
||||
private socket$: WebSocketSubject<WebSocketMessage>;
|
||||
private messageSubject$: Subject<WebSocketMessage>;
|
||||
private connectionStatus$: BehaviorSubject<boolean>;
|
||||
private reconnectAttempts = 0;
|
||||
private readonly maxReconnectAttempts = 5;
|
||||
|
||||
callback: {[key: string]: Function} = {}
|
||||
|
||||
constructor() {
|
||||
this.messageSubject$ = new Subject<WebSocketMessage>();
|
||||
this.connectionStatus$ = new BehaviorSubject<boolean>(false);
|
||||
|
||||
this.connect('https://5-180-182-151.cloud-xip.com:85/ws/')
|
||||
|
||||
this.messages$.subscribe(({payload, requestId, type}) => {
|
||||
if(this.callback[requestId]) {
|
||||
this.callback[requestId]({payload, requestId, type})
|
||||
delete this.callback[requestId]
|
||||
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
public connect(url: string) {
|
||||
this.socket$ = webSocket<WebSocketMessage>(url);
|
||||
|
||||
this.socket$.pipe(
|
||||
tap({
|
||||
|
||||
error: () => {
|
||||
this.connectionStatus$.next(false);
|
||||
}
|
||||
}),
|
||||
retryWhen(errors => errors.pipe(
|
||||
tap(() => {
|
||||
this.reconnectAttempts++;
|
||||
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
||||
throw new Error('Max reconnect attempts reached');
|
||||
}
|
||||
}),
|
||||
delay(1000)
|
||||
))
|
||||
).subscribe(
|
||||
(message) => {
|
||||
this.messageSubject$.next(message);
|
||||
|
||||
if (!this.connectionStatus$.getValue()) {
|
||||
this.connectionStatus$.next(true);
|
||||
this.reconnectAttempts = 0;
|
||||
|
||||
// Send a message when the connection is established
|
||||
this.sendMessage(SessionStore.user.UserId as any).subscribe();
|
||||
}
|
||||
},
|
||||
(err) => {
|
||||
console.error('WebSocket connection error:', err);
|
||||
},
|
||||
() => {
|
||||
console.log('WebSocket connection closed');
|
||||
this.connectionStatus$.next(false);
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
public sendMessage(message: WebSocketMessage): Observable<any> {
|
||||
return new Observable<void>(observer => {
|
||||
|
||||
|
||||
|
||||
if(typeof message == 'object') {
|
||||
message.requestId = uuidv4()
|
||||
this.socket$.next(message);
|
||||
|
||||
this.callback[message.requestId] = ({payload, requestId})=> {
|
||||
observer.next(payload as any);
|
||||
observer.complete();
|
||||
}
|
||||
|
||||
} else {
|
||||
this.socket$.next(message);
|
||||
observer.next({} as any);
|
||||
observer.complete();
|
||||
}
|
||||
|
||||
|
||||
|
||||
}).pipe(
|
||||
catchError(err => {
|
||||
console.error('Send message error:', err);
|
||||
return new Observable<never>(observer => {
|
||||
observer.error({ type: 'SEND_ERROR', error: err });
|
||||
});
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
public get messages$(): Observable<WebSocketMessage> {
|
||||
return this.messageSubject$.asObservable();
|
||||
}
|
||||
|
||||
public get connectionStatus(): Observable<boolean> {
|
||||
return this.connectionStatus$.asObservable();
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user